diff --git a/Examples/v2/echo/Echo.swift b/Examples/v2/echo/Echo.swift deleted file mode 100644 index 8ff07f420..000000000 --- a/Examples/v2/echo/Echo.swift +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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 ArgumentParser - -@main -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -struct Echo: AsyncParsableCommand { - static let configuration = CommandConfiguration( - commandName: "echo", - abstract: "A multi-tool to run an echo server and execute RPCs against it.", - subcommands: [Serve.self, Get.self, Collect.self, Expand.self, Update.self] - ) -} diff --git a/Examples/v2/echo/Generated/echo.grpc.swift b/Examples/v2/echo/Generated/echo.grpc.swift deleted file mode 100644 index 63a264205..000000000 --- a/Examples/v2/echo/Generated/echo.grpc.swift +++ /dev/null @@ -1,493 +0,0 @@ -// Copyright (c) 2015, Google 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. - -// DO NOT EDIT. -// swift-format-ignore-file -// -// Generated by the gRPC Swift generator plugin for the protocol buffer compiler. -// Source: echo.proto -// -// For information on using the generated types, please see the documentation: -// https://github.com/grpc/grpc-swift - -import GRPCCore -import GRPCProtobuf - -internal enum Echo_Echo { - internal static let descriptor = GRPCCore.ServiceDescriptor.echo_Echo - internal enum Method { - internal enum Get { - internal typealias Input = Echo_EchoRequest - internal typealias Output = Echo_EchoResponse - internal static let descriptor = GRPCCore.MethodDescriptor( - service: Echo_Echo.descriptor.fullyQualifiedService, - method: "Get" - ) - } - internal enum Expand { - internal typealias Input = Echo_EchoRequest - internal typealias Output = Echo_EchoResponse - internal static let descriptor = GRPCCore.MethodDescriptor( - service: Echo_Echo.descriptor.fullyQualifiedService, - method: "Expand" - ) - } - internal enum Collect { - internal typealias Input = Echo_EchoRequest - internal typealias Output = Echo_EchoResponse - internal static let descriptor = GRPCCore.MethodDescriptor( - service: Echo_Echo.descriptor.fullyQualifiedService, - method: "Collect" - ) - } - internal enum Update { - internal typealias Input = Echo_EchoRequest - internal typealias Output = Echo_EchoResponse - internal static let descriptor = GRPCCore.MethodDescriptor( - service: Echo_Echo.descriptor.fullyQualifiedService, - method: "Update" - ) - } - internal static let descriptors: [GRPCCore.MethodDescriptor] = [ - Get.descriptor, - Expand.descriptor, - Collect.descriptor, - Update.descriptor - ] - } - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - internal typealias StreamingServiceProtocol = Echo_EchoStreamingServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - internal typealias ServiceProtocol = Echo_EchoServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - internal typealias ClientProtocol = Echo_EchoClientProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - internal typealias Client = Echo_EchoClient -} - -extension GRPCCore.ServiceDescriptor { - internal static let echo_Echo = Self( - package: "echo", - service: "Echo" - ) -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -internal protocol Echo_EchoStreamingServiceProtocol: GRPCCore.RegistrableRPCService { - /// Immediately returns an echo of a request. - func get( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream - - /// Splits a request into words and returns each word in a stream of messages. - func expand( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream - - /// Collects a stream of messages and returns them concatenated when the caller closes. - func collect( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream - - /// Streams back messages as they are received in an input stream. - func update( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream -} - -/// Conformance to `GRPCCore.RegistrableRPCService`. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension Echo_Echo.StreamingServiceProtocol { - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - internal func registerMethods(with router: inout GRPCCore.RPCRouter) { - router.registerHandler( - forMethod: Echo_Echo.Method.Get.descriptor, - deserializer: GRPCProtobuf.ProtobufDeserializer(), - serializer: GRPCProtobuf.ProtobufSerializer(), - handler: { request, context in - try await self.get( - request: request, - context: context - ) - } - ) - router.registerHandler( - forMethod: Echo_Echo.Method.Expand.descriptor, - deserializer: GRPCProtobuf.ProtobufDeserializer(), - serializer: GRPCProtobuf.ProtobufSerializer(), - handler: { request, context in - try await self.expand( - request: request, - context: context - ) - } - ) - router.registerHandler( - forMethod: Echo_Echo.Method.Collect.descriptor, - deserializer: GRPCProtobuf.ProtobufDeserializer(), - serializer: GRPCProtobuf.ProtobufSerializer(), - handler: { request, context in - try await self.collect( - request: request, - context: context - ) - } - ) - router.registerHandler( - forMethod: Echo_Echo.Method.Update.descriptor, - deserializer: GRPCProtobuf.ProtobufDeserializer(), - serializer: GRPCProtobuf.ProtobufSerializer(), - handler: { request, context in - try await self.update( - request: request, - context: context - ) - } - ) - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -internal protocol Echo_EchoServiceProtocol: Echo_Echo.StreamingServiceProtocol { - /// Immediately returns an echo of a request. - func get( - request: GRPCCore.ServerRequest.Single, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Single - - /// Splits a request into words and returns each word in a stream of messages. - func expand( - request: GRPCCore.ServerRequest.Single, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream - - /// Collects a stream of messages and returns them concatenated when the caller closes. - func collect( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Single - - /// Streams back messages as they are received in an input stream. - func update( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream -} - -/// Partial conformance to `Echo_EchoStreamingServiceProtocol`. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension Echo_Echo.ServiceProtocol { - internal func get( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream { - let response = try await self.get( - request: GRPCCore.ServerRequest.Single(stream: request), - context: context - ) - return GRPCCore.ServerResponse.Stream(single: response) - } - - internal func expand( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream { - let response = try await self.expand( - request: GRPCCore.ServerRequest.Single(stream: request), - context: context - ) - return response - } - - internal func collect( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream { - let response = try await self.collect( - request: request, - context: context - ) - return GRPCCore.ServerResponse.Stream(single: response) - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -internal protocol Echo_EchoClientProtocol: Sendable { - /// Immediately returns an echo of a request. - func get( - request: GRPCCore.ClientRequest.Single, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R - ) async throws -> R where R: Sendable - - /// Splits a request into words and returns each word in a stream of messages. - func expand( - request: GRPCCore.ClientRequest.Single, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Stream) async throws -> R - ) async throws -> R where R: Sendable - - /// Collects a stream of messages and returns them concatenated when the caller closes. - func collect( - request: GRPCCore.ClientRequest.Stream, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R - ) async throws -> R where R: Sendable - - /// Streams back messages as they are received in an input stream. - func update( - request: GRPCCore.ClientRequest.Stream, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Stream) async throws -> R - ) async throws -> R where R: Sendable -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension Echo_Echo.ClientProtocol { - internal func get( - request: GRPCCore.ClientRequest.Single, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R = { - try $0.message - } - ) async throws -> R where R: Sendable { - try await self.get( - request: request, - serializer: GRPCProtobuf.ProtobufSerializer(), - deserializer: GRPCProtobuf.ProtobufDeserializer(), - options: options, - body - ) - } - - internal func expand( - request: GRPCCore.ClientRequest.Single, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Stream) async throws -> R - ) async throws -> R where R: Sendable { - try await self.expand( - request: request, - serializer: GRPCProtobuf.ProtobufSerializer(), - deserializer: GRPCProtobuf.ProtobufDeserializer(), - options: options, - body - ) - } - - internal func collect( - request: GRPCCore.ClientRequest.Stream, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R = { - try $0.message - } - ) async throws -> R where R: Sendable { - try await self.collect( - request: request, - serializer: GRPCProtobuf.ProtobufSerializer(), - deserializer: GRPCProtobuf.ProtobufDeserializer(), - options: options, - body - ) - } - - internal func update( - request: GRPCCore.ClientRequest.Stream, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Stream) async throws -> R - ) async throws -> R where R: Sendable { - try await self.update( - request: request, - serializer: GRPCProtobuf.ProtobufSerializer(), - deserializer: GRPCProtobuf.ProtobufDeserializer(), - options: options, - body - ) - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension Echo_Echo.ClientProtocol { - /// Immediately returns an echo of a request. - internal func get( - _ message: Echo_EchoRequest, - metadata: GRPCCore.Metadata = [:], - options: GRPCCore.CallOptions = .defaults, - onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> Result = { - try $0.message - } - ) async throws -> Result where Result: Sendable { - let request = GRPCCore.ClientRequest.Single( - message: message, - metadata: metadata - ) - return try await self.get( - request: request, - options: options, - handleResponse - ) - } - - /// Splits a request into words and returns each word in a stream of messages. - internal func expand( - _ message: Echo_EchoRequest, - metadata: GRPCCore.Metadata = [:], - options: GRPCCore.CallOptions = .defaults, - onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse.Stream) async throws -> Result - ) async throws -> Result where Result: Sendable { - let request = GRPCCore.ClientRequest.Single( - message: message, - metadata: metadata - ) - return try await self.expand( - request: request, - options: options, - handleResponse - ) - } - - /// Collects a stream of messages and returns them concatenated when the caller closes. - internal func collect( - metadata: GRPCCore.Metadata = [:], - options: GRPCCore.CallOptions = .defaults, - requestProducer: @Sendable @escaping (GRPCCore.RPCWriter) async throws -> Void, - onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> Result = { - try $0.message - } - ) async throws -> Result where Result: Sendable { - let request = GRPCCore.ClientRequest.Stream( - metadata: metadata, - producer: requestProducer - ) - return try await self.collect( - request: request, - options: options, - handleResponse - ) - } - - /// Streams back messages as they are received in an input stream. - internal func update( - metadata: GRPCCore.Metadata = [:], - options: GRPCCore.CallOptions = .defaults, - requestProducer: @Sendable @escaping (GRPCCore.RPCWriter) async throws -> Void, - onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse.Stream) async throws -> Result - ) async throws -> Result where Result: Sendable { - let request = GRPCCore.ClientRequest.Stream( - metadata: metadata, - producer: requestProducer - ) - return try await self.update( - request: request, - options: options, - handleResponse - ) - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -internal struct Echo_EchoClient: Echo_Echo.ClientProtocol { - private let client: GRPCCore.GRPCClient - - internal init(wrapping client: GRPCCore.GRPCClient) { - self.client = client - } - - /// Immediately returns an echo of a request. - internal func get( - request: GRPCCore.ClientRequest.Single, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R = { - try $0.message - } - ) async throws -> R where R: Sendable { - try await self.client.unary( - request: request, - descriptor: Echo_Echo.Method.Get.descriptor, - serializer: serializer, - deserializer: deserializer, - options: options, - handler: body - ) - } - - /// Splits a request into words and returns each word in a stream of messages. - internal func expand( - request: GRPCCore.ClientRequest.Single, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Stream) async throws -> R - ) async throws -> R where R: Sendable { - try await self.client.serverStreaming( - request: request, - descriptor: Echo_Echo.Method.Expand.descriptor, - serializer: serializer, - deserializer: deserializer, - options: options, - handler: body - ) - } - - /// Collects a stream of messages and returns them concatenated when the caller closes. - internal func collect( - request: GRPCCore.ClientRequest.Stream, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R = { - try $0.message - } - ) async throws -> R where R: Sendable { - try await self.client.clientStreaming( - request: request, - descriptor: Echo_Echo.Method.Collect.descriptor, - serializer: serializer, - deserializer: deserializer, - options: options, - handler: body - ) - } - - /// Streams back messages as they are received in an input stream. - internal func update( - request: GRPCCore.ClientRequest.Stream, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Stream) async throws -> R - ) async throws -> R where R: Sendable { - try await self.client.bidirectionalStreaming( - request: request, - descriptor: Echo_Echo.Method.Update.descriptor, - serializer: serializer, - deserializer: deserializer, - options: options, - handler: body - ) - } -} \ No newline at end of file diff --git a/Examples/v2/echo/Generated/echo.pb.swift b/Examples/v2/echo/Generated/echo.pb.swift deleted file mode 100644 index 88ef21ca9..000000000 --- a/Examples/v2/echo/Generated/echo.pb.swift +++ /dev/null @@ -1,129 +0,0 @@ -// DO NOT EDIT. -// swift-format-ignore-file -// swiftlint:disable all -// -// Generated by the Swift generator plugin for the protocol buffer compiler. -// Source: echo.proto -// -// For information on using the generated types, please see the documentation: -// https://github.com/apple/swift-protobuf/ - -// Copyright (c) 2015, Google 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 - -// If the compiler emits an error on this type, it is because this file -// was generated by a version of the `protoc` Swift plug-in that is -// incompatible with the version of SwiftProtobuf to which you are linking. -// Please ensure that you are building against the same version of the API -// that was used to generate this file. -fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { - struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} - typealias Version = _2 -} - -struct Echo_EchoRequest: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// The text of a message to be echoed. - var text: String = String() - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -struct Echo_EchoResponse: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// The text of an echo response. - var text: String = String() - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -// MARK: - Code below here is support for the SwiftProtobuf runtime. - -fileprivate let _protobuf_package = "echo" - -extension Echo_EchoRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".EchoRequest" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "text"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularStringField(value: &self.text) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if !self.text.isEmpty { - try visitor.visitSingularStringField(value: self.text, fieldNumber: 1) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Echo_EchoRequest, rhs: Echo_EchoRequest) -> Bool { - if lhs.text != rhs.text {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Echo_EchoResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".EchoResponse" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "text"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularStringField(value: &self.text) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if !self.text.isEmpty { - try visitor.visitSingularStringField(value: self.text, fieldNumber: 1) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Echo_EchoResponse, rhs: Echo_EchoResponse) -> Bool { - if lhs.text != rhs.text {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} diff --git a/Examples/v2/echo/Subcommands/ClientArguments.swift b/Examples/v2/echo/Subcommands/ClientArguments.swift deleted file mode 100644 index 7dea8e59f..000000000 --- a/Examples/v2/echo/Subcommands/ClientArguments.swift +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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 ArgumentParser -import GRPCHTTP2Core - -struct ClientArguments: ParsableArguments { - @Option(help: "The server's listening port") - var port: Int = 1234 - - @Option(help: "The number of times to repeat the call") - var repetitions: Int = 1 - - @Option(help: "Message to send to the server") - var message: String -} - -extension ClientArguments { - var target: any ResolvableTarget { - return .ipv4(host: "127.0.0.1", port: self.port) - } -} diff --git a/Examples/v2/echo/Subcommands/Collect.swift b/Examples/v2/echo/Subcommands/Collect.swift deleted file mode 100644 index 3a61915df..000000000 --- a/Examples/v2/echo/Subcommands/Collect.swift +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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 ArgumentParser -import GRPCCore -import GRPCHTTP2Core -import GRPCHTTP2TransportNIOPosix - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -struct Collect: AsyncParsableCommand { - static let configuration = CommandConfiguration( - abstract: "Makes a client streaming RPC to the echo server." - ) - - @OptionGroup - var arguments: ClientArguments - - func run() async throws { - let client = GRPCClient( - transport: try .http2NIOPosix( - target: self.arguments.target, - config: .defaults(transportSecurity: .plaintext) - ) - ) - - try await withThrowingDiscardingTaskGroup { group in - group.addTask { - try await client.run() - } - - let echo = Echo_EchoClient(wrapping: client) - - for _ in 0 ..< self.arguments.repetitions { - let message = try await echo.collect { writer in - for part in self.arguments.message.split(separator: " ") { - print("collect → \(part)") - try await writer.write(.with { $0.text = String(part) }) - } - } - print("collect ← \(message.text)") - } - - client.beginGracefulShutdown() - } - } -} diff --git a/Examples/v2/echo/Subcommands/Expand.swift b/Examples/v2/echo/Subcommands/Expand.swift deleted file mode 100644 index 1d06bdd99..000000000 --- a/Examples/v2/echo/Subcommands/Expand.swift +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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 ArgumentParser -import GRPCCore -import GRPCHTTP2Core -import GRPCHTTP2TransportNIOPosix - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -struct Expand: AsyncParsableCommand { - static let configuration = CommandConfiguration( - abstract: "Makes a server streaming RPC to the echo server." - ) - - @OptionGroup - var arguments: ClientArguments - - func run() async throws { - let client = GRPCClient( - transport: try .http2NIOPosix( - target: self.arguments.target, - config: .defaults(transportSecurity: .plaintext) - ) - ) - - try await withThrowingDiscardingTaskGroup { group in - group.addTask { - try await client.run() - } - - let echo = Echo_EchoClient(wrapping: client) - - for _ in 0 ..< self.arguments.repetitions { - let message = Echo_EchoRequest.with { $0.text = self.arguments.message } - print("expand → \(message.text)") - try await echo.expand(message) { response in - for try await message in response.messages { - print("expand ← \(message.text)") - } - } - } - - client.beginGracefulShutdown() - } - } -} diff --git a/Examples/v2/echo/Subcommands/Get.swift b/Examples/v2/echo/Subcommands/Get.swift deleted file mode 100644 index 0dd551002..000000000 --- a/Examples/v2/echo/Subcommands/Get.swift +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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 ArgumentParser -import GRPCCore -import GRPCHTTP2Core -import GRPCHTTP2TransportNIOPosix - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -struct Get: AsyncParsableCommand { - static let configuration = CommandConfiguration(abstract: "Makes a unary RPC to the echo server.") - - @OptionGroup - var arguments: ClientArguments - - func run() async throws { - let client = GRPCClient( - transport: try .http2NIOPosix( - target: self.arguments.target, - config: .defaults(transportSecurity: .plaintext) - ) - ) - - try await withThrowingDiscardingTaskGroup { group in - group.addTask { - try await client.run() - } - - let echo = Echo_EchoClient(wrapping: client) - - for _ in 0 ..< self.arguments.repetitions { - let message = Echo_EchoRequest.with { $0.text = self.arguments.message } - print("get → \(message.text)") - let response = try await echo.get(message) - print("get ← \(response.text)") - } - - client.beginGracefulShutdown() - } - } -} diff --git a/Examples/v2/echo/Subcommands/Serve.swift b/Examples/v2/echo/Subcommands/Serve.swift deleted file mode 100644 index 5bfa1772f..000000000 --- a/Examples/v2/echo/Subcommands/Serve.swift +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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 ArgumentParser -import GRPCCore -import GRPCHTTP2Core -import GRPCHTTP2TransportNIOPosix - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -struct Serve: AsyncParsableCommand { - static let configuration = CommandConfiguration(abstract: "Starts an echo server.") - - @Option(help: "The port to listen on") - var port: Int = 1234 - - func run() async throws { - let server = GRPCServer( - transport: .http2NIOPosix( - address: .ipv4(host: "127.0.0.1", port: self.port), - config: .defaults(transportSecurity: .plaintext) - ), - services: [EchoService()] - ) - - try await withThrowingDiscardingTaskGroup { group in - group.addTask { try await server.serve() } - if let address = try await server.listeningAddress { - print("Echo listening on \(address)") - } - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -struct EchoService: Echo_EchoServiceProtocol { - func get( - request: ServerRequest.Single, - context: ServerContext - ) async throws -> ServerResponse.Single { - return ServerResponse.Single(message: .with { $0.text = request.message.text }) - } - - func collect( - request: ServerRequest.Stream, - context: ServerContext - ) async throws -> ServerResponse.Single { - let messages = try await request.messages.reduce(into: []) { $0.append($1.text) } - let joined = messages.joined(separator: " ") - return ServerResponse.Single(message: .with { $0.text = joined }) - } - - func expand( - request: ServerRequest.Single, - context: ServerContext - ) async throws -> ServerResponse.Stream { - return ServerResponse.Stream { writer in - let parts = request.message.text.split(separator: " ") - let messages = parts.map { part in Echo_EchoResponse.with { $0.text = String(part) } } - try await writer.write(contentsOf: messages) - return [:] - } - } - - func update( - request: ServerRequest.Stream, - context: ServerContext - ) async throws -> ServerResponse.Stream { - return ServerResponse.Stream { writer in - for try await message in request.messages { - try await writer.write(.with { $0.text = message.text }) - } - return [:] - } - } -} diff --git a/Examples/v2/echo/Subcommands/Update.swift b/Examples/v2/echo/Subcommands/Update.swift deleted file mode 100644 index 1c189caa8..000000000 --- a/Examples/v2/echo/Subcommands/Update.swift +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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 ArgumentParser -import GRPCCore -import GRPCHTTP2Core -import GRPCHTTP2TransportNIOPosix - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -struct Update: AsyncParsableCommand { - static let configuration = CommandConfiguration( - abstract: "Makes a bidirectional server streaming RPC to the echo server." - ) - - @OptionGroup - var arguments: ClientArguments - - func run() async throws { - let client = GRPCClient( - transport: try .http2NIOPosix( - target: self.arguments.target, - config: .defaults(transportSecurity: .plaintext) - ) - ) - - try await withThrowingDiscardingTaskGroup { group in - group.addTask { - try await client.run() - } - - let echo = Echo_EchoClient(wrapping: client) - - for _ in 0 ..< self.arguments.repetitions { - try await echo.update { writer in - for part in self.arguments.message.split(separator: " ") { - print("update → \(part)") - try await writer.write(.with { $0.text = String(part) }) - } - } onResponse: { response in - for try await message in response.messages { - print("update ← \(message.text)") - } - } - } - - client.beginGracefulShutdown() - } - } -} diff --git a/Examples/v2/hello-world/Generated/helloworld.grpc.swift b/Examples/v2/hello-world/Generated/helloworld.grpc.swift deleted file mode 100644 index 8044411e5..000000000 --- a/Examples/v2/hello-world/Generated/helloworld.grpc.swift +++ /dev/null @@ -1,196 +0,0 @@ -/// Copyright 2015 gRPC authors. -/// -/// 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. - -// DO NOT EDIT. -// swift-format-ignore-file -// -// Generated by the gRPC Swift generator plugin for the protocol buffer compiler. -// Source: helloworld.proto -// -// For information on using the generated types, please see the documentation: -// https://github.com/grpc/grpc-swift - -import GRPCCore -import GRPCProtobuf - -internal enum Helloworld_Greeter { - internal static let descriptor = GRPCCore.ServiceDescriptor.helloworld_Greeter - internal enum Method { - internal enum SayHello { - internal typealias Input = Helloworld_HelloRequest - internal typealias Output = Helloworld_HelloReply - internal static let descriptor = GRPCCore.MethodDescriptor( - service: Helloworld_Greeter.descriptor.fullyQualifiedService, - method: "SayHello" - ) - } - internal static let descriptors: [GRPCCore.MethodDescriptor] = [ - SayHello.descriptor - ] - } - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - internal typealias StreamingServiceProtocol = Helloworld_GreeterStreamingServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - internal typealias ServiceProtocol = Helloworld_GreeterServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - internal typealias ClientProtocol = Helloworld_GreeterClientProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - internal typealias Client = Helloworld_GreeterClient -} - -extension GRPCCore.ServiceDescriptor { - internal static let helloworld_Greeter = Self( - package: "helloworld", - service: "Greeter" - ) -} - -/// The greeting service definition. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -internal protocol Helloworld_GreeterStreamingServiceProtocol: GRPCCore.RegistrableRPCService { - /// Sends a greeting - func sayHello( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream -} - -/// Conformance to `GRPCCore.RegistrableRPCService`. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension Helloworld_Greeter.StreamingServiceProtocol { - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - internal func registerMethods(with router: inout GRPCCore.RPCRouter) { - router.registerHandler( - forMethod: Helloworld_Greeter.Method.SayHello.descriptor, - deserializer: GRPCProtobuf.ProtobufDeserializer(), - serializer: GRPCProtobuf.ProtobufSerializer(), - handler: { request, context in - try await self.sayHello( - request: request, - context: context - ) - } - ) - } -} - -/// The greeting service definition. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -internal protocol Helloworld_GreeterServiceProtocol: Helloworld_Greeter.StreamingServiceProtocol { - /// Sends a greeting - func sayHello( - request: GRPCCore.ServerRequest.Single, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Single -} - -/// Partial conformance to `Helloworld_GreeterStreamingServiceProtocol`. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension Helloworld_Greeter.ServiceProtocol { - internal func sayHello( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream { - let response = try await self.sayHello( - request: GRPCCore.ServerRequest.Single(stream: request), - context: context - ) - return GRPCCore.ServerResponse.Stream(single: response) - } -} - -/// The greeting service definition. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -internal protocol Helloworld_GreeterClientProtocol: Sendable { - /// Sends a greeting - func sayHello( - request: GRPCCore.ClientRequest.Single, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R - ) async throws -> R where R: Sendable -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension Helloworld_Greeter.ClientProtocol { - internal func sayHello( - request: GRPCCore.ClientRequest.Single, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R = { - try $0.message - } - ) async throws -> R where R: Sendable { - try await self.sayHello( - request: request, - serializer: GRPCProtobuf.ProtobufSerializer(), - deserializer: GRPCProtobuf.ProtobufDeserializer(), - options: options, - body - ) - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension Helloworld_Greeter.ClientProtocol { - /// Sends a greeting - internal func sayHello( - _ message: Helloworld_HelloRequest, - metadata: GRPCCore.Metadata = [:], - options: GRPCCore.CallOptions = .defaults, - onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> Result = { - try $0.message - } - ) async throws -> Result where Result: Sendable { - let request = GRPCCore.ClientRequest.Single( - message: message, - metadata: metadata - ) - return try await self.sayHello( - request: request, - options: options, - handleResponse - ) - } -} - -/// The greeting service definition. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -internal struct Helloworld_GreeterClient: Helloworld_Greeter.ClientProtocol { - private let client: GRPCCore.GRPCClient - - internal init(wrapping client: GRPCCore.GRPCClient) { - self.client = client - } - - /// Sends a greeting - internal func sayHello( - request: GRPCCore.ClientRequest.Single, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R = { - try $0.message - } - ) async throws -> R where R: Sendable { - try await self.client.unary( - request: request, - descriptor: Helloworld_Greeter.Method.SayHello.descriptor, - serializer: serializer, - deserializer: deserializer, - options: options, - handler: body - ) - } -} \ No newline at end of file diff --git a/Examples/v2/hello-world/Generated/helloworld.pb.swift b/Examples/v2/hello-world/Generated/helloworld.pb.swift deleted file mode 100644 index 20b4f36df..000000000 --- a/Examples/v2/hello-world/Generated/helloworld.pb.swift +++ /dev/null @@ -1,129 +0,0 @@ -// DO NOT EDIT. -// swift-format-ignore-file -// swiftlint:disable all -// -// Generated by the Swift generator plugin for the protocol buffer compiler. -// Source: helloworld.proto -// -// For information on using the generated types, please see the documentation: -// https://github.com/apple/swift-protobuf/ - -/// Copyright 2015 gRPC authors. -/// -/// 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 - -// If the compiler emits an error on this type, it is because this file -// was generated by a version of the `protoc` Swift plug-in that is -// incompatible with the version of SwiftProtobuf to which you are linking. -// Please ensure that you are building against the same version of the API -// that was used to generate this file. -fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { - struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} - typealias Version = _2 -} - -/// The request message containing the user's name. -struct Helloworld_HelloRequest: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var name: String = String() - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -/// The response message containing the greetings -struct Helloworld_HelloReply: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var message: String = String() - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -// MARK: - Code below here is support for the SwiftProtobuf runtime. - -fileprivate let _protobuf_package = "helloworld" - -extension Helloworld_HelloRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".HelloRequest" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "name"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularStringField(value: &self.name) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if !self.name.isEmpty { - try visitor.visitSingularStringField(value: self.name, fieldNumber: 1) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Helloworld_HelloRequest, rhs: Helloworld_HelloRequest) -> Bool { - if lhs.name != rhs.name {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Helloworld_HelloReply: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".HelloReply" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "message"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularStringField(value: &self.message) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if !self.message.isEmpty { - try visitor.visitSingularStringField(value: self.message, fieldNumber: 1) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Helloworld_HelloReply, rhs: Helloworld_HelloReply) -> Bool { - if lhs.message != rhs.message {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} diff --git a/Examples/v2/hello-world/HelloWorld.proto b/Examples/v2/hello-world/HelloWorld.proto deleted file mode 120000 index f746c2066..000000000 --- a/Examples/v2/hello-world/HelloWorld.proto +++ /dev/null @@ -1 +0,0 @@ -../../../Protos/upstream/grpc/examples/helloworld.proto \ No newline at end of file diff --git a/Examples/v2/hello-world/HelloWorld.swift b/Examples/v2/hello-world/HelloWorld.swift deleted file mode 100644 index 8d467670a..000000000 --- a/Examples/v2/hello-world/HelloWorld.swift +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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 ArgumentParser - -@main -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -struct HelloWorld: AsyncParsableCommand { - static let configuration = CommandConfiguration( - commandName: "hello-world", - abstract: "A multi-tool to run a greeter server and execute RPCs against it.", - subcommands: [Serve.self, Greet.self] - ) -} diff --git a/Examples/v2/hello-world/Subcommands/Greet.swift b/Examples/v2/hello-world/Subcommands/Greet.swift deleted file mode 100644 index 069b8faee..000000000 --- a/Examples/v2/hello-world/Subcommands/Greet.swift +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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 ArgumentParser -import GRPCHTTP2Transport -import GRPCProtobuf - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -struct Greet: AsyncParsableCommand { - static let configuration = CommandConfiguration(abstract: "Sends a request to the greeter server") - - @Option(help: "The port to listen on") - var port: Int = 31415 - - @Option(help: "The person to greet") - var name: String = "" - - func run() async throws { - try await withThrowingDiscardingTaskGroup { group in - let client = GRPCClient( - transport: try .http2NIOPosix( - target: .ipv4(host: "127.0.0.1", port: self.port), - config: .defaults(transportSecurity: .plaintext) - ) - ) - - group.addTask { - try await client.run() - } - - defer { - client.beginGracefulShutdown() - } - - let greeter = Helloworld_GreeterClient(wrapping: client) - let reply = try await greeter.sayHello(.with { $0.name = self.name }) - print(reply.message) - } - } -} diff --git a/Examples/v2/hello-world/Subcommands/Serve.swift b/Examples/v2/hello-world/Subcommands/Serve.swift deleted file mode 100644 index a9dd178ec..000000000 --- a/Examples/v2/hello-world/Subcommands/Serve.swift +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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 ArgumentParser -import GRPCHTTP2Transport -import GRPCProtobuf - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -struct Serve: AsyncParsableCommand { - static let configuration = CommandConfiguration(abstract: "Starts a greeter server.") - - @Option(help: "The port to listen on") - var port: Int = 31415 - - func run() async throws { - let server = GRPCServer( - transport: .http2NIOPosix( - address: .ipv4(host: "127.0.0.1", port: self.port), - config: .defaults(transportSecurity: .plaintext) - ), - services: [Greeter()] - ) - - try await withThrowingDiscardingTaskGroup { group in - group.addTask { try await server.serve() } - if let address = try await server.listeningAddress { - print("Greeter listening on \(address)") - } - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -struct Greeter: Helloworld_GreeterServiceProtocol { - func sayHello( - request: ServerRequest.Single, - context: ServerContext - ) async throws -> ServerResponse.Single { - var reply = Helloworld_HelloReply() - let recipient = request.message.name.isEmpty ? "stranger" : request.message.name - reply.message = "Hello, \(recipient)" - return ServerResponse.Single(message: reply) - } -} diff --git a/Examples/v2/route-guide/Generated/route_guide.grpc.swift b/Examples/v2/route-guide/Generated/route_guide.grpc.swift deleted file mode 100644 index 6a89ed2a3..000000000 --- a/Examples/v2/route-guide/Generated/route_guide.grpc.swift +++ /dev/null @@ -1,577 +0,0 @@ -// Copyright 2015 gRPC authors. -// -// 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. - -// DO NOT EDIT. -// swift-format-ignore-file -// -// Generated by the gRPC Swift generator plugin for the protocol buffer compiler. -// Source: route_guide.proto -// -// For information on using the generated types, please see the documentation: -// https://github.com/grpc/grpc-swift - -import GRPCCore -import GRPCProtobuf - -internal enum Routeguide_RouteGuide { - internal static let descriptor = GRPCCore.ServiceDescriptor.routeguide_RouteGuide - internal enum Method { - internal enum GetFeature { - internal typealias Input = Routeguide_Point - internal typealias Output = Routeguide_Feature - internal static let descriptor = GRPCCore.MethodDescriptor( - service: Routeguide_RouteGuide.descriptor.fullyQualifiedService, - method: "GetFeature" - ) - } - internal enum ListFeatures { - internal typealias Input = Routeguide_Rectangle - internal typealias Output = Routeguide_Feature - internal static let descriptor = GRPCCore.MethodDescriptor( - service: Routeguide_RouteGuide.descriptor.fullyQualifiedService, - method: "ListFeatures" - ) - } - internal enum RecordRoute { - internal typealias Input = Routeguide_Point - internal typealias Output = Routeguide_RouteSummary - internal static let descriptor = GRPCCore.MethodDescriptor( - service: Routeguide_RouteGuide.descriptor.fullyQualifiedService, - method: "RecordRoute" - ) - } - internal enum RouteChat { - internal typealias Input = Routeguide_RouteNote - internal typealias Output = Routeguide_RouteNote - internal static let descriptor = GRPCCore.MethodDescriptor( - service: Routeguide_RouteGuide.descriptor.fullyQualifiedService, - method: "RouteChat" - ) - } - internal static let descriptors: [GRPCCore.MethodDescriptor] = [ - GetFeature.descriptor, - ListFeatures.descriptor, - RecordRoute.descriptor, - RouteChat.descriptor - ] - } - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - internal typealias StreamingServiceProtocol = Routeguide_RouteGuideStreamingServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - internal typealias ServiceProtocol = Routeguide_RouteGuideServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - internal typealias ClientProtocol = Routeguide_RouteGuideClientProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - internal typealias Client = Routeguide_RouteGuideClient -} - -extension GRPCCore.ServiceDescriptor { - internal static let routeguide_RouteGuide = Self( - package: "routeguide", - service: "RouteGuide" - ) -} - -/// Interface exported by the server. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -internal protocol Routeguide_RouteGuideStreamingServiceProtocol: GRPCCore.RegistrableRPCService { - /// A simple RPC. - /// - /// Obtains the feature at a given position. - /// - /// A feature with an empty name is returned if there's no feature at the given - /// position. - func getFeature( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream - - /// A server-to-client streaming RPC. - /// - /// Obtains the Features available within the given Rectangle. Results are - /// streamed rather than returned at once (e.g. in a response message with a - /// repeated field), as the rectangle may cover a large area and contain a - /// huge number of features. - func listFeatures( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream - - /// A client-to-server streaming RPC. - /// - /// Accepts a stream of Points on a route being traversed, returning a - /// RouteSummary when traversal is completed. - func recordRoute( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream - - /// A Bidirectional streaming RPC. - /// - /// Accepts a stream of RouteNotes sent while a route is being traversed, - /// while receiving other RouteNotes (e.g. from other users). - func routeChat( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream -} - -/// Conformance to `GRPCCore.RegistrableRPCService`. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension Routeguide_RouteGuide.StreamingServiceProtocol { - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - internal func registerMethods(with router: inout GRPCCore.RPCRouter) { - router.registerHandler( - forMethod: Routeguide_RouteGuide.Method.GetFeature.descriptor, - deserializer: GRPCProtobuf.ProtobufDeserializer(), - serializer: GRPCProtobuf.ProtobufSerializer(), - handler: { request, context in - try await self.getFeature( - request: request, - context: context - ) - } - ) - router.registerHandler( - forMethod: Routeguide_RouteGuide.Method.ListFeatures.descriptor, - deserializer: GRPCProtobuf.ProtobufDeserializer(), - serializer: GRPCProtobuf.ProtobufSerializer(), - handler: { request, context in - try await self.listFeatures( - request: request, - context: context - ) - } - ) - router.registerHandler( - forMethod: Routeguide_RouteGuide.Method.RecordRoute.descriptor, - deserializer: GRPCProtobuf.ProtobufDeserializer(), - serializer: GRPCProtobuf.ProtobufSerializer(), - handler: { request, context in - try await self.recordRoute( - request: request, - context: context - ) - } - ) - router.registerHandler( - forMethod: Routeguide_RouteGuide.Method.RouteChat.descriptor, - deserializer: GRPCProtobuf.ProtobufDeserializer(), - serializer: GRPCProtobuf.ProtobufSerializer(), - handler: { request, context in - try await self.routeChat( - request: request, - context: context - ) - } - ) - } -} - -/// Interface exported by the server. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -internal protocol Routeguide_RouteGuideServiceProtocol: Routeguide_RouteGuide.StreamingServiceProtocol { - /// A simple RPC. - /// - /// Obtains the feature at a given position. - /// - /// A feature with an empty name is returned if there's no feature at the given - /// position. - func getFeature( - request: GRPCCore.ServerRequest.Single, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Single - - /// A server-to-client streaming RPC. - /// - /// Obtains the Features available within the given Rectangle. Results are - /// streamed rather than returned at once (e.g. in a response message with a - /// repeated field), as the rectangle may cover a large area and contain a - /// huge number of features. - func listFeatures( - request: GRPCCore.ServerRequest.Single, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream - - /// A client-to-server streaming RPC. - /// - /// Accepts a stream of Points on a route being traversed, returning a - /// RouteSummary when traversal is completed. - func recordRoute( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Single - - /// A Bidirectional streaming RPC. - /// - /// Accepts a stream of RouteNotes sent while a route is being traversed, - /// while receiving other RouteNotes (e.g. from other users). - func routeChat( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream -} - -/// Partial conformance to `Routeguide_RouteGuideStreamingServiceProtocol`. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension Routeguide_RouteGuide.ServiceProtocol { - internal func getFeature( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream { - let response = try await self.getFeature( - request: GRPCCore.ServerRequest.Single(stream: request), - context: context - ) - return GRPCCore.ServerResponse.Stream(single: response) - } - - internal func listFeatures( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream { - let response = try await self.listFeatures( - request: GRPCCore.ServerRequest.Single(stream: request), - context: context - ) - return response - } - - internal func recordRoute( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream { - let response = try await self.recordRoute( - request: request, - context: context - ) - return GRPCCore.ServerResponse.Stream(single: response) - } -} - -/// Interface exported by the server. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -internal protocol Routeguide_RouteGuideClientProtocol: Sendable { - /// A simple RPC. - /// - /// Obtains the feature at a given position. - /// - /// A feature with an empty name is returned if there's no feature at the given - /// position. - func getFeature( - request: GRPCCore.ClientRequest.Single, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R - ) async throws -> R where R: Sendable - - /// A server-to-client streaming RPC. - /// - /// Obtains the Features available within the given Rectangle. Results are - /// streamed rather than returned at once (e.g. in a response message with a - /// repeated field), as the rectangle may cover a large area and contain a - /// huge number of features. - func listFeatures( - request: GRPCCore.ClientRequest.Single, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Stream) async throws -> R - ) async throws -> R where R: Sendable - - /// A client-to-server streaming RPC. - /// - /// Accepts a stream of Points on a route being traversed, returning a - /// RouteSummary when traversal is completed. - func recordRoute( - request: GRPCCore.ClientRequest.Stream, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R - ) async throws -> R where R: Sendable - - /// A Bidirectional streaming RPC. - /// - /// Accepts a stream of RouteNotes sent while a route is being traversed, - /// while receiving other RouteNotes (e.g. from other users). - func routeChat( - request: GRPCCore.ClientRequest.Stream, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Stream) async throws -> R - ) async throws -> R where R: Sendable -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension Routeguide_RouteGuide.ClientProtocol { - internal func getFeature( - request: GRPCCore.ClientRequest.Single, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R = { - try $0.message - } - ) async throws -> R where R: Sendable { - try await self.getFeature( - request: request, - serializer: GRPCProtobuf.ProtobufSerializer(), - deserializer: GRPCProtobuf.ProtobufDeserializer(), - options: options, - body - ) - } - - internal func listFeatures( - request: GRPCCore.ClientRequest.Single, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Stream) async throws -> R - ) async throws -> R where R: Sendable { - try await self.listFeatures( - request: request, - serializer: GRPCProtobuf.ProtobufSerializer(), - deserializer: GRPCProtobuf.ProtobufDeserializer(), - options: options, - body - ) - } - - internal func recordRoute( - request: GRPCCore.ClientRequest.Stream, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R = { - try $0.message - } - ) async throws -> R where R: Sendable { - try await self.recordRoute( - request: request, - serializer: GRPCProtobuf.ProtobufSerializer(), - deserializer: GRPCProtobuf.ProtobufDeserializer(), - options: options, - body - ) - } - - internal func routeChat( - request: GRPCCore.ClientRequest.Stream, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Stream) async throws -> R - ) async throws -> R where R: Sendable { - try await self.routeChat( - request: request, - serializer: GRPCProtobuf.ProtobufSerializer(), - deserializer: GRPCProtobuf.ProtobufDeserializer(), - options: options, - body - ) - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension Routeguide_RouteGuide.ClientProtocol { - /// A simple RPC. - /// - /// Obtains the feature at a given position. - /// - /// A feature with an empty name is returned if there's no feature at the given - /// position. - internal func getFeature( - _ message: Routeguide_Point, - metadata: GRPCCore.Metadata = [:], - options: GRPCCore.CallOptions = .defaults, - onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> Result = { - try $0.message - } - ) async throws -> Result where Result: Sendable { - let request = GRPCCore.ClientRequest.Single( - message: message, - metadata: metadata - ) - return try await self.getFeature( - request: request, - options: options, - handleResponse - ) - } - - /// A server-to-client streaming RPC. - /// - /// Obtains the Features available within the given Rectangle. Results are - /// streamed rather than returned at once (e.g. in a response message with a - /// repeated field), as the rectangle may cover a large area and contain a - /// huge number of features. - internal func listFeatures( - _ message: Routeguide_Rectangle, - metadata: GRPCCore.Metadata = [:], - options: GRPCCore.CallOptions = .defaults, - onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse.Stream) async throws -> Result - ) async throws -> Result where Result: Sendable { - let request = GRPCCore.ClientRequest.Single( - message: message, - metadata: metadata - ) - return try await self.listFeatures( - request: request, - options: options, - handleResponse - ) - } - - /// A client-to-server streaming RPC. - /// - /// Accepts a stream of Points on a route being traversed, returning a - /// RouteSummary when traversal is completed. - internal func recordRoute( - metadata: GRPCCore.Metadata = [:], - options: GRPCCore.CallOptions = .defaults, - requestProducer: @Sendable @escaping (GRPCCore.RPCWriter) async throws -> Void, - onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> Result = { - try $0.message - } - ) async throws -> Result where Result: Sendable { - let request = GRPCCore.ClientRequest.Stream( - metadata: metadata, - producer: requestProducer - ) - return try await self.recordRoute( - request: request, - options: options, - handleResponse - ) - } - - /// A Bidirectional streaming RPC. - /// - /// Accepts a stream of RouteNotes sent while a route is being traversed, - /// while receiving other RouteNotes (e.g. from other users). - internal func routeChat( - metadata: GRPCCore.Metadata = [:], - options: GRPCCore.CallOptions = .defaults, - requestProducer: @Sendable @escaping (GRPCCore.RPCWriter) async throws -> Void, - onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse.Stream) async throws -> Result - ) async throws -> Result where Result: Sendable { - let request = GRPCCore.ClientRequest.Stream( - metadata: metadata, - producer: requestProducer - ) - return try await self.routeChat( - request: request, - options: options, - handleResponse - ) - } -} - -/// Interface exported by the server. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -internal struct Routeguide_RouteGuideClient: Routeguide_RouteGuide.ClientProtocol { - private let client: GRPCCore.GRPCClient - - internal init(wrapping client: GRPCCore.GRPCClient) { - self.client = client - } - - /// A simple RPC. - /// - /// Obtains the feature at a given position. - /// - /// A feature with an empty name is returned if there's no feature at the given - /// position. - internal func getFeature( - request: GRPCCore.ClientRequest.Single, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R = { - try $0.message - } - ) async throws -> R where R: Sendable { - try await self.client.unary( - request: request, - descriptor: Routeguide_RouteGuide.Method.GetFeature.descriptor, - serializer: serializer, - deserializer: deserializer, - options: options, - handler: body - ) - } - - /// A server-to-client streaming RPC. - /// - /// Obtains the Features available within the given Rectangle. Results are - /// streamed rather than returned at once (e.g. in a response message with a - /// repeated field), as the rectangle may cover a large area and contain a - /// huge number of features. - internal func listFeatures( - request: GRPCCore.ClientRequest.Single, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Stream) async throws -> R - ) async throws -> R where R: Sendable { - try await self.client.serverStreaming( - request: request, - descriptor: Routeguide_RouteGuide.Method.ListFeatures.descriptor, - serializer: serializer, - deserializer: deserializer, - options: options, - handler: body - ) - } - - /// A client-to-server streaming RPC. - /// - /// Accepts a stream of Points on a route being traversed, returning a - /// RouteSummary when traversal is completed. - internal func recordRoute( - request: GRPCCore.ClientRequest.Stream, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R = { - try $0.message - } - ) async throws -> R where R: Sendable { - try await self.client.clientStreaming( - request: request, - descriptor: Routeguide_RouteGuide.Method.RecordRoute.descriptor, - serializer: serializer, - deserializer: deserializer, - options: options, - handler: body - ) - } - - /// A Bidirectional streaming RPC. - /// - /// Accepts a stream of RouteNotes sent while a route is being traversed, - /// while receiving other RouteNotes (e.g. from other users). - internal func routeChat( - request: GRPCCore.ClientRequest.Stream, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Stream) async throws -> R - ) async throws -> R where R: Sendable { - try await self.client.bidirectionalStreaming( - request: request, - descriptor: Routeguide_RouteGuide.Method.RouteChat.descriptor, - serializer: serializer, - deserializer: deserializer, - options: options, - handler: body - ) - } -} \ No newline at end of file diff --git a/Examples/v2/route-guide/Generated/route_guide.pb.swift b/Examples/v2/route-guide/Generated/route_guide.pb.swift deleted file mode 100644 index 09892ef83..000000000 --- a/Examples/v2/route-guide/Generated/route_guide.pb.swift +++ /dev/null @@ -1,387 +0,0 @@ -// DO NOT EDIT. -// swift-format-ignore-file -// swiftlint:disable all -// -// Generated by the Swift generator plugin for the protocol buffer compiler. -// Source: route_guide.proto -// -// For information on using the generated types, please see the documentation: -// https://github.com/apple/swift-protobuf/ - -// Copyright 2015 gRPC authors. -// -// 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 - -// If the compiler emits an error on this type, it is because this file -// was generated by a version of the `protoc` Swift plug-in that is -// incompatible with the version of SwiftProtobuf to which you are linking. -// Please ensure that you are building against the same version of the API -// that was used to generate this file. -fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { - struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} - typealias Version = _2 -} - -/// Points are represented as latitude-longitude pairs in the E7 representation -/// (degrees multiplied by 10**7 and rounded to the nearest integer). -/// Latitudes should be in the range +/- 90 degrees and longitude should be in -/// the range +/- 180 degrees (inclusive). -struct Routeguide_Point: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var latitude: Int32 = 0 - - var longitude: Int32 = 0 - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -/// A latitude-longitude rectangle, represented as two diagonally opposite -/// points "lo" and "hi". -struct Routeguide_Rectangle: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// One corner of the rectangle. - var lo: Routeguide_Point { - get {return _lo ?? Routeguide_Point()} - set {_lo = newValue} - } - /// Returns true if `lo` has been explicitly set. - var hasLo: Bool {return self._lo != nil} - /// Clears the value of `lo`. Subsequent reads from it will return its default value. - mutating func clearLo() {self._lo = nil} - - /// The other corner of the rectangle. - var hi: Routeguide_Point { - get {return _hi ?? Routeguide_Point()} - set {_hi = newValue} - } - /// Returns true if `hi` has been explicitly set. - var hasHi: Bool {return self._hi != nil} - /// Clears the value of `hi`. Subsequent reads from it will return its default value. - mutating func clearHi() {self._hi = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _lo: Routeguide_Point? = nil - fileprivate var _hi: Routeguide_Point? = nil -} - -/// A feature names something at a given point. -/// -/// If a feature could not be named, the name is empty. -struct Routeguide_Feature: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// The name of the feature. - var name: String = String() - - /// The point where the feature is detected. - var location: Routeguide_Point { - get {return _location ?? Routeguide_Point()} - set {_location = newValue} - } - /// Returns true if `location` has been explicitly set. - var hasLocation: Bool {return self._location != nil} - /// Clears the value of `location`. Subsequent reads from it will return its default value. - mutating func clearLocation() {self._location = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _location: Routeguide_Point? = nil -} - -/// A RouteNote is a message sent while at a given point. -struct Routeguide_RouteNote: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// The location from which the message is sent. - var location: Routeguide_Point { - get {return _location ?? Routeguide_Point()} - set {_location = newValue} - } - /// Returns true if `location` has been explicitly set. - var hasLocation: Bool {return self._location != nil} - /// Clears the value of `location`. Subsequent reads from it will return its default value. - mutating func clearLocation() {self._location = nil} - - /// The message to be sent. - var message: String = String() - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _location: Routeguide_Point? = nil -} - -/// A RouteSummary is received in response to a RecordRoute rpc. -/// -/// It contains the number of individual points received, the number of -/// detected features, and the total distance covered as the cumulative sum of -/// the distance between each point. -struct Routeguide_RouteSummary: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// The number of points received. - var pointCount: Int32 = 0 - - /// The number of known features passed while traversing the route. - var featureCount: Int32 = 0 - - /// The distance covered in metres. - var distance: Int32 = 0 - - /// The duration of the traversal in seconds. - var elapsedTime: Int32 = 0 - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -// MARK: - Code below here is support for the SwiftProtobuf runtime. - -fileprivate let _protobuf_package = "routeguide" - -extension Routeguide_Point: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".Point" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "latitude"), - 2: .same(proto: "longitude"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularInt32Field(value: &self.latitude) }() - case 2: try { try decoder.decodeSingularInt32Field(value: &self.longitude) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if self.latitude != 0 { - try visitor.visitSingularInt32Field(value: self.latitude, fieldNumber: 1) - } - if self.longitude != 0 { - try visitor.visitSingularInt32Field(value: self.longitude, fieldNumber: 2) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Routeguide_Point, rhs: Routeguide_Point) -> Bool { - if lhs.latitude != rhs.latitude {return false} - if lhs.longitude != rhs.longitude {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Routeguide_Rectangle: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".Rectangle" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "lo"), - 2: .same(proto: "hi"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularMessageField(value: &self._lo) }() - case 2: try { try decoder.decodeSingularMessageField(value: &self._hi) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 - try { if let v = self._lo { - try visitor.visitSingularMessageField(value: v, fieldNumber: 1) - } }() - try { if let v = self._hi { - try visitor.visitSingularMessageField(value: v, fieldNumber: 2) - } }() - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Routeguide_Rectangle, rhs: Routeguide_Rectangle) -> Bool { - if lhs._lo != rhs._lo {return false} - if lhs._hi != rhs._hi {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Routeguide_Feature: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".Feature" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "name"), - 2: .same(proto: "location"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularStringField(value: &self.name) }() - case 2: try { try decoder.decodeSingularMessageField(value: &self._location) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 - if !self.name.isEmpty { - try visitor.visitSingularStringField(value: self.name, fieldNumber: 1) - } - try { if let v = self._location { - try visitor.visitSingularMessageField(value: v, fieldNumber: 2) - } }() - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Routeguide_Feature, rhs: Routeguide_Feature) -> Bool { - if lhs.name != rhs.name {return false} - if lhs._location != rhs._location {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Routeguide_RouteNote: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".RouteNote" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "location"), - 2: .same(proto: "message"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularMessageField(value: &self._location) }() - case 2: try { try decoder.decodeSingularStringField(value: &self.message) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 - try { if let v = self._location { - try visitor.visitSingularMessageField(value: v, fieldNumber: 1) - } }() - if !self.message.isEmpty { - try visitor.visitSingularStringField(value: self.message, fieldNumber: 2) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Routeguide_RouteNote, rhs: Routeguide_RouteNote) -> Bool { - if lhs._location != rhs._location {return false} - if lhs.message != rhs.message {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Routeguide_RouteSummary: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".RouteSummary" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "point_count"), - 2: .standard(proto: "feature_count"), - 3: .same(proto: "distance"), - 4: .standard(proto: "elapsed_time"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularInt32Field(value: &self.pointCount) }() - case 2: try { try decoder.decodeSingularInt32Field(value: &self.featureCount) }() - case 3: try { try decoder.decodeSingularInt32Field(value: &self.distance) }() - case 4: try { try decoder.decodeSingularInt32Field(value: &self.elapsedTime) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if self.pointCount != 0 { - try visitor.visitSingularInt32Field(value: self.pointCount, fieldNumber: 1) - } - if self.featureCount != 0 { - try visitor.visitSingularInt32Field(value: self.featureCount, fieldNumber: 2) - } - if self.distance != 0 { - try visitor.visitSingularInt32Field(value: self.distance, fieldNumber: 3) - } - if self.elapsedTime != 0 { - try visitor.visitSingularInt32Field(value: self.elapsedTime, fieldNumber: 4) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Routeguide_RouteSummary, rhs: Routeguide_RouteSummary) -> Bool { - if lhs.pointCount != rhs.pointCount {return false} - if lhs.featureCount != rhs.featureCount {return false} - if lhs.distance != rhs.distance {return false} - if lhs.elapsedTime != rhs.elapsedTime {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} diff --git a/Examples/v2/route-guide/RouteGuide.swift b/Examples/v2/route-guide/RouteGuide.swift deleted file mode 100644 index d53882726..000000000 --- a/Examples/v2/route-guide/RouteGuide.swift +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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 ArgumentParser - -@main -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -struct RouteGuide: AsyncParsableCommand { - static let configuration = CommandConfiguration( - commandName: "route-guide", - subcommands: [Serve.self, GetFeature.self, ListFeatures.self, RecordRoute.self, RouteChat.self] - ) -} diff --git a/Examples/v2/route-guide/Subcommands/GetFeature.swift b/Examples/v2/route-guide/Subcommands/GetFeature.swift deleted file mode 100644 index 1c42ec6da..000000000 --- a/Examples/v2/route-guide/Subcommands/GetFeature.swift +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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 ArgumentParser -import GRPCHTTP2Transport - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -struct GetFeature: AsyncParsableCommand { - static let configuration = CommandConfiguration(abstract: "Gets a feature at a given location.") - - @Option(help: "The server's listening port") - var port: Int = 31415 - - @Option( - name: [.customLong("latitude"), .customLong("lat")], - help: "Latitude of the feature to get in E7 format (degrees ⨯ 1e7)" - ) - var latitude: Int32 = 407_838_351 - - @Option( - name: [.customLong("longitude"), .customLong("lon")], - help: "Longitude of the feature to get in E7 format (degrees ⨯ 1e7)" - ) - var longitude: Int32 = -746_143_763 - - func run() async throws { - let transport = try HTTP2ClientTransport.Posix( - target: .ipv4(host: "127.0.0.1", port: self.port), - config: .defaults(transportSecurity: .plaintext) - ) - let client = GRPCClient(transport: transport) - - try await withThrowingDiscardingTaskGroup { group in - group.addTask { - try await client.run() - } - - let routeGuide = Routeguide_RouteGuideClient(wrapping: client) - - let point = Routeguide_Point.with { - $0.latitude = self.latitude - $0.longitude = self.longitude - } - - let feature = try await routeGuide.getFeature(point) - - if feature.name.isEmpty { - print("No feature found at (\(self.latitude), \(self.longitude))") - } else { - print("Found '\(feature.name)' at (\(self.latitude), \(self.longitude))") - } - - client.beginGracefulShutdown() - } - } -} diff --git a/Examples/v2/route-guide/Subcommands/ListFeatures.swift b/Examples/v2/route-guide/Subcommands/ListFeatures.swift deleted file mode 100644 index 887a944e2..000000000 --- a/Examples/v2/route-guide/Subcommands/ListFeatures.swift +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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 ArgumentParser -import GRPCHTTP2Transport - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -struct ListFeatures: AsyncParsableCommand { - static let configuration = CommandConfiguration( - abstract: "List all features within a bounding rectangle." - ) - - @Option(help: "The server's listening port") - var port: Int = 31415 - - @Option( - name: [.customLong("minimum-latitude"), .customLong("min-lat")], - help: "Minimum latitude of the bounding rectangle to search in E7 format." - ) - var minLatitude: Int32 = 400_000_000 - - @Option( - name: [.customLong("maximum-latitude"), .customLong("max-lat")], - help: "Maximum latitude of the bounding rectangle to search in E7 format." - ) - var maxLatitude: Int32 = 420_000_000 - - @Option( - name: [.customLong("minimum-longitude"), .customLong("min-lon")], - help: "Minimum longitude of the bounding rectangle to search in E7 format." - ) - var minLongitude: Int32 = -750_000_000 - - @Option( - name: [.customLong("maximum-longitude"), .customLong("max-lon")], - help: "Maximum longitude of the bounding rectangle to search in E7 format." - ) - var maxLongitude: Int32 = -730_000_000 - - func run() async throws { - let transport = try HTTP2ClientTransport.Posix( - target: .ipv4(host: "127.0.0.1", port: self.port), - config: .defaults(transportSecurity: .plaintext) - ) - let client = GRPCClient(transport: transport) - - try await withThrowingDiscardingTaskGroup { group in - group.addTask { - try await client.run() - } - - let routeGuide = Routeguide_RouteGuideClient(wrapping: client) - let boundingRectangle = Routeguide_Rectangle.with { - $0.lo.latitude = self.minLatitude - $0.hi.latitude = self.maxLatitude - $0.lo.longitude = self.minLongitude - $0.hi.longitude = self.maxLongitude - } - - try await routeGuide.listFeatures(boundingRectangle) { response in - var count = 0 - for try await feature in response.messages { - count += 1 - let (lat, lon) = (feature.location.latitude, feature.location.longitude) - print("(\(count)) \(feature.name) at (\(lat), \(lon))") - } - } - - client.beginGracefulShutdown() - } - - } -} diff --git a/Examples/v2/route-guide/Subcommands/RecordRoute.swift b/Examples/v2/route-guide/Subcommands/RecordRoute.swift deleted file mode 100644 index cd443230b..000000000 --- a/Examples/v2/route-guide/Subcommands/RecordRoute.swift +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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 ArgumentParser -import GRPCHTTP2Transport - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -struct RecordRoute: AsyncParsableCommand { - static let configuration = CommandConfiguration( - abstract: "Records a route by visiting N randomly selected points and prints a summary of it." - ) - - @Option(help: "The server's listening port") - var port: Int = 31415 - - @Option(help: "The number of places to visit.") - var points: Int = 10 - - func run() async throws { - let transport = try HTTP2ClientTransport.Posix( - target: .ipv4(host: "127.0.0.1", port: self.port), - config: .defaults(transportSecurity: .plaintext) - ) - let client = GRPCClient(transport: transport) - - try await withThrowingDiscardingTaskGroup { group in - group.addTask { - try await client.run() - } - - let routeGuide = Routeguide_RouteGuideClient(wrapping: client) - - // Get all features. - let rectangle = Routeguide_Rectangle.with { - $0.lo.latitude = 400_000_000 - $0.hi.latitude = 420_000_000 - $0.lo.longitude = -750_000_000 - $0.hi.longitude = -730_000_000 - } - let features = try await routeGuide.listFeatures(rectangle) { response in - try await response.messages.reduce(into: []) { $0.append($1) } - } - - // Pick 'N' locations to visit. - let placesToVisit = features.shuffled().map { $0.location }.prefix(self.points) - - // Record a route. - let summary = try await routeGuide.recordRoute { writer in - try await writer.write(contentsOf: placesToVisit) - } - - let text = """ - Visited \(summary.pointCount) points and \(summary.featureCount) features covering \ - a distance \(summary.distance) metres. - """ - print(text) - - client.beginGracefulShutdown() - } - } -} diff --git a/Examples/v2/route-guide/Subcommands/RouteChat.swift b/Examples/v2/route-guide/Subcommands/RouteChat.swift deleted file mode 100644 index 81cb5c3e2..000000000 --- a/Examples/v2/route-guide/Subcommands/RouteChat.swift +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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 ArgumentParser -import GRPCHTTP2Transport - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -struct RouteChat: AsyncParsableCommand { - static let configuration = CommandConfiguration( - abstract: """ - Visits a few points and records a note at each, and prints all notes previously recorded at \ - each point. - """ - ) - - @Option(help: "The server's listening port") - var port: Int = 31415 - - func run() async throws { - let transport = try HTTP2ClientTransport.Posix( - target: .ipv4(host: "127.0.0.1", port: self.port), - config: .defaults(transportSecurity: .plaintext) - ) - let client = GRPCClient(transport: transport) - - try await withThrowingDiscardingTaskGroup { group in - group.addTask { - try await client.run() - } - - let routeGuide = Routeguide_RouteGuideClient(wrapping: client) - - try await routeGuide.routeChat { writer in - let notes: [(String, (Int32, Int32))] = [ - ("First message", (0, 0)), - ("Second message", (0, 1)), - ("Third message", (1, 0)), - ("Fourth message", (0, 0)), - ("Fifth message", (1, 0)), - ] - - for (message, (lat, lon)) in notes { - let note = Routeguide_RouteNote.with { - $0.message = message - $0.location.latitude = lat - $0.location.longitude = lon - } - print("Sending note: '\(message) at (\(lat), \(lon))'") - try await writer.write(note) - } - } onResponse: { response in - for try await note in response.messages { - let (lat, lon) = (note.location.latitude, note.location.longitude) - print("Received note: '\(note.message) at (\(lat), \(lon))'") - } - } - - client.beginGracefulShutdown() - } - } -} diff --git a/Examples/v2/route-guide/Subcommands/Serve.swift b/Examples/v2/route-guide/Subcommands/Serve.swift deleted file mode 100644 index f9b942876..000000000 --- a/Examples/v2/route-guide/Subcommands/Serve.swift +++ /dev/null @@ -1,250 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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 ArgumentParser -import Foundation -import GRPCHTTP2Transport -import GRPCProtobuf -import Synchronization - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -struct Serve: AsyncParsableCommand { - static let configuration = CommandConfiguration(abstract: "Starts a route-guide server.") - - @Option(help: "The port to listen on") - var port: Int = 31415 - - private func loadFeatures() throws -> [Routeguide_Feature] { - guard let url = Bundle.module.url(forResource: "route_guide_db", withExtension: "json") else { - throw RPCError(code: .internalError, message: "Couldn't find 'route_guide_db.json") - } - - let data = try Data(contentsOf: url) - return try Routeguide_Feature.array(fromJSONUTF8Bytes: data) - } - - func run() async throws { - let features = try self.loadFeatures() - let transport = HTTP2ServerTransport.Posix( - address: .ipv4(host: "127.0.0.1", port: self.port), - config: .defaults(transportSecurity: .plaintext) - ) - - let server = GRPCServer(transport: transport, services: [RouteGuideService(features: features)]) - try await withThrowingDiscardingTaskGroup { group in - group.addTask { try await server.serve() } - let address = try await transport.listeningAddress - print("server listening on \(address)") - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -struct RouteGuideService { - /// Known features. - private let features: [Routeguide_Feature] - /// Notes recorded by clients. - private let receivedNotes: Notes - - /// A thread-safe store for notes sent by clients. - private final class Notes: Sendable { - private let notes: Mutex<[Routeguide_RouteNote]> - - init() { - self.notes = Mutex([]) - } - - /// Records a note and returns all other notes recorded at the same location. - /// - /// - Parameter receivedNote: A note to record. - /// - Returns: Other notes recorded at the same location. - func recordNote(_ receivedNote: Routeguide_RouteNote) -> [Routeguide_RouteNote] { - return self.notes.withLock { notes in - var notesFromSameLocation: [Routeguide_RouteNote] = [] - for note in notes { - if note.location == receivedNote.location { - notesFromSameLocation.append(note) - } - } - notes.append(receivedNote) - return notesFromSameLocation - } - } - } - - /// Creates a new route guide service. - /// - Parameter features: Known features. - init(features: [Routeguide_Feature]) { - self.features = features - self.receivedNotes = Notes() - } - - /// Returns the first feature found at the given location, if one exists. - private func findFeature(latitude: Int32, longitude: Int32) -> Routeguide_Feature? { - self.features.first { - $0.location.latitude == latitude && $0.location.longitude == longitude - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension RouteGuideService: Routeguide_RouteGuide.ServiceProtocol { - func getFeature( - request: ServerRequest.Single, - context: ServerContext - ) async throws -> ServerResponse.Single { - let feature = self.findFeature( - latitude: request.message.latitude, - longitude: request.message.longitude - ) - - if let feature { - return ServerResponse.Single(message: feature) - } else { - // No feature: return a feature with an empty name. - let unknownFeature = Routeguide_Feature.with { - $0.name = "" - $0.location = .with { - $0.latitude = request.message.latitude - $0.longitude = request.message.longitude - } - } - return ServerResponse.Single(message: unknownFeature) - } - } - - func listFeatures( - request: ServerRequest.Single, - context: ServerContext - ) async throws -> ServerResponse.Stream { - return ServerResponse.Stream { writer in - let featuresWithinBounds = self.features.filter { feature in - !feature.name.isEmpty && feature.isContained(by: request.message) - } - - try await writer.write(contentsOf: featuresWithinBounds) - return [:] - } - } - - func recordRoute( - request: ServerRequest.Stream, - context: ServerContext - ) async throws -> ServerResponse.Single { - let startTime = ContinuousClock.now - var pointsVisited = 0 - var featuresVisited = 0 - var distanceTravelled = 0.0 - var previousPoint: Routeguide_Point? = nil - - for try await point in request.messages { - pointsVisited += 1 - - if self.findFeature(latitude: point.latitude, longitude: point.longitude) != nil { - featuresVisited += 1 - } - - if let previousPoint { - distanceTravelled += greatCircleDistance(from: previousPoint, to: point) - } - - previousPoint = point - } - - let duration = startTime.duration(to: .now) - let summary = Routeguide_RouteSummary.with { - $0.pointCount = Int32(pointsVisited) - $0.featureCount = Int32(featuresVisited) - $0.elapsedTime = Int32(duration.components.seconds) - $0.distance = Int32(distanceTravelled) - } - - return ServerResponse.Single(message: summary) - } - - func routeChat( - request: ServerRequest.Stream, - context: ServerContext - ) async throws -> ServerResponse.Stream { - return ServerResponse.Stream { writer in - for try await note in request.messages { - let notes = self.receivedNotes.recordNote(note) - try await writer.write(contentsOf: notes) - } - return [:] - } - } -} - -extension Routeguide_Feature { - func isContained( - by rectangle: Routeguide_Rectangle - ) -> Bool { - return rectangle.lo.latitude <= self.location.latitude - && self.location.latitude <= rectangle.hi.latitude - && rectangle.lo.longitude <= self.location.longitude - && self.location.longitude <= rectangle.hi.longitude - } -} - -private func greatCircleDistance( - from point1: Routeguide_Point, - to point2: Routeguide_Point -) -> Double { - // See https://en.wikipedia.org/wiki/Great-circle_distance - // - // Let λ1 (lambda1) and φ1 (phi1) be the longitude and latitude of point 1. - // Let λ2 (lambda2) and φ2 (phi2) be the longitude and latitude of point 2. - // - // Let Δλ = λ2 - λ1, and Δφ = φ2 - φ1. - // - // The central angle between them, σc (sigmaC) can be computed as: - // - // σc = 2 ⨯ sqrt(sin²(Δφ/2) + cos(φ1) ⨯ cos(φ2) ⨯ sin²(Δλ/2)) - // - // The unit distance (d) between point1 and point2 can then be computed as: - // - // d = 2 ⨯ atan(sqrt(σc), sqrt(1 - σc)) - - let lambda1 = radians(degreesInE7: point1.longitude) - let phi1 = radians(degreesInE7: point1.latitude) - let lambda2 = radians(degreesInE7: point2.longitude) - let phi2 = radians(degreesInE7: point2.latitude) - - // Δλ = λ2 - λ1 - let deltaLambda = lambda2 - lambda1 - // Δφ = φ2 - φ1 - let deltaPhi = phi2 - phi1 - - // sin²(Δφ/2) - let sinHalfDeltaPhiSquared = sin(deltaPhi / 2) * sin(deltaPhi / 2) - // sin²(Δλ/2) - let sinHalfDeltaLambdaSquared = sin(deltaLambda / 2) * sin(deltaLambda / 2) - - // σc = 2 ⨯ sqrt(sin²(Δφ/2) + cos(φ1) ⨯ cos(φ2) ⨯ sin²(Δλ/2)) - let sigmaC = 2 * sqrt(sinHalfDeltaPhiSquared + cos(phi1) * cos(phi2) * sinHalfDeltaLambdaSquared) - - // This is the unit distance, i.e. assumes the circle has a radius of 1. - let unitDistance = 2 * atan2(sqrt(sigmaC), sqrt(1 - sigmaC)) - - // Scale it by the radius of the Earth in meters. - let earthRadius = 6_371_000.0 - return unitDistance * earthRadius -} - -private func radians(degreesInE7 degrees: Int32) -> Double { - return (Double(degrees) / 1e7) * .pi / 180.0 -} diff --git a/Examples/v2/route-guide/route_guide_db.json b/Examples/v2/route-guide/route_guide_db.json deleted file mode 100644 index 22e93e313..000000000 --- a/Examples/v2/route-guide/route_guide_db.json +++ /dev/null @@ -1,702 +0,0 @@ -[ - { - "location": { - "latitude": 407838351, - "longitude": -746143763 - }, - "name": "Patriots Path, Mendham, NJ 07945, USA" - }, - { - "location": { - "latitude": 408122808, - "longitude": -743999179 - }, - "name": "101 New Jersey 10, Whippany, NJ 07981, USA" - }, - { - "location": { - "latitude": 413628156, - "longitude": -749015468 - }, - "name": "U.S. 6, Shohola, PA 18458, USA" - }, - { - "location": { - "latitude": 419999544, - "longitude": -740371136 - }, - "name": "5 Conners Road, Kingston, NY 12401, USA" - }, - { - "location": { - "latitude": 414008389, - "longitude": -743951297 - }, - "name": "Mid Hudson Psychiatric Center, New Hampton, NY 10958, USA" - }, - { - "location": { - "latitude": 419611318, - "longitude": -746524769 - }, - "name": "287 Flugertown Road, Livingston Manor, NY 12758, USA" - }, - { - "location": { - "latitude": 406109563, - "longitude": -742186778 - }, - "name": "4001 Tremley Point Road, Linden, NJ 07036, USA" - }, - { - "location": { - "latitude": 416802456, - "longitude": -742370183 - }, - "name": "352 South Mountain Road, Wallkill, NY 12589, USA" - }, - { - "location": { - "latitude": 412950425, - "longitude": -741077389 - }, - "name": "Bailey Turn Road, Harriman, NY 10926, USA" - }, - { - "location": { - "latitude": 412144655, - "longitude": -743949739 - }, - "name": "193-199 Wawayanda Road, Hewitt, NJ 07421, USA" - }, - { - "location": { - "latitude": 415736605, - "longitude": -742847522 - }, - "name": "406-496 Ward Avenue, Pine Bush, NY 12566, USA" - }, - { - "location": { - "latitude": 413843930, - "longitude": -740501726 - }, - "name": "162 Merrill Road, Highland Mills, NY 10930, USA" - }, - { - "location": { - "latitude": 410873075, - "longitude": -744459023 - }, - "name": "Clinton Road, West Milford, NJ 07480, USA" - }, - { - "location": { - "latitude": 412346009, - "longitude": -744026814 - }, - "name": "16 Old Brook Lane, Warwick, NY 10990, USA" - }, - { - "location": { - "latitude": 402948455, - "longitude": -747903913 - }, - "name": "3 Drake Lane, Pennington, NJ 08534, USA" - }, - { - "location": { - "latitude": 406337092, - "longitude": -740122226 - }, - "name": "6324 8th Avenue, Brooklyn, NY 11220, USA" - }, - { - "location": { - "latitude": 406421967, - "longitude": -747727624 - }, - "name": "1 Merck Access Road, Whitehouse Station, NJ 08889, USA" - }, - { - "location": { - "latitude": 416318082, - "longitude": -749677716 - }, - "name": "78-98 Schalck Road, Narrowsburg, NY 12764, USA" - }, - { - "location": { - "latitude": 415301720, - "longitude": -748416257 - }, - "name": "282 Lakeview Drive Road, Highland Lake, NY 12743, USA" - }, - { - "location": { - "latitude": 402647019, - "longitude": -747071791 - }, - "name": "330 Evelyn Avenue, Hamilton Township, NJ 08619, USA" - }, - { - "location": { - "latitude": 412567807, - "longitude": -741058078 - }, - "name": "New York State Reference Route 987E, Southfields, NY 10975, USA" - }, - { - "location": { - "latitude": 416855156, - "longitude": -744420597 - }, - "name": "103-271 Tempaloni Road, Ellenville, NY 12428, USA" - }, - { - "location": { - "latitude": 404663628, - "longitude": -744820157 - }, - "name": "1300 Airport Road, North Brunswick Township, NJ 08902, USA" - }, - { - "location": { - "latitude": 407113723, - "longitude": -749746483 - }, - "name": "" - }, - { - "location": { - "latitude": 402133926, - "longitude": -743613249 - }, - "name": "" - }, - { - "location": { - "latitude": 400273442, - "longitude": -741220915 - }, - "name": "" - }, - { - "location": { - "latitude": 411236786, - "longitude": -744070769 - }, - "name": "" - }, - { - "location": { - "latitude": 411633782, - "longitude": -746784970 - }, - "name": "211-225 Plains Road, Augusta, NJ 07822, USA" - }, - { - "location": { - "latitude": 415830701, - "longitude": -742952812 - }, - "name": "" - }, - { - "location": { - "latitude": 413447164, - "longitude": -748712898 - }, - "name": "165 Pedersen Ridge Road, Milford, PA 18337, USA" - }, - { - "location": { - "latitude": 405047245, - "longitude": -749800722 - }, - "name": "100-122 Locktown Road, Frenchtown, NJ 08825, USA" - }, - { - "location": { - "latitude": 418858923, - "longitude": -746156790 - }, - "name": "" - }, - { - "location": { - "latitude": 417951888, - "longitude": -748484944 - }, - "name": "650-652 Willi Hill Road, Swan Lake, NY 12783, USA" - }, - { - "location": { - "latitude": 407033786, - "longitude": -743977337 - }, - "name": "26 East 3rd Street, New Providence, NJ 07974, USA" - }, - { - "location": { - "latitude": 417548014, - "longitude": -740075041 - }, - "name": "" - }, - { - "location": { - "latitude": 410395868, - "longitude": -744972325 - }, - "name": "" - }, - { - "location": { - "latitude": 404615353, - "longitude": -745129803 - }, - "name": "" - }, - { - "location": { - "latitude": 406589790, - "longitude": -743560121 - }, - "name": "611 Lawrence Avenue, Westfield, NJ 07090, USA" - }, - { - "location": { - "latitude": 414653148, - "longitude": -740477477 - }, - "name": "18 Lannis Avenue, New Windsor, NY 12553, USA" - }, - { - "location": { - "latitude": 405957808, - "longitude": -743255336 - }, - "name": "82-104 Amherst Avenue, Colonia, NJ 07067, USA" - }, - { - "location": { - "latitude": 411733589, - "longitude": -741648093 - }, - "name": "170 Seven Lakes Drive, Sloatsburg, NY 10974, USA" - }, - { - "location": { - "latitude": 412676291, - "longitude": -742606606 - }, - "name": "1270 Lakes Road, Monroe, NY 10950, USA" - }, - { - "location": { - "latitude": 409224445, - "longitude": -748286738 - }, - "name": "509-535 Alphano Road, Great Meadows, NJ 07838, USA" - }, - { - "location": { - "latitude": 406523420, - "longitude": -742135517 - }, - "name": "652 Garden Street, Elizabeth, NJ 07202, USA" - }, - { - "location": { - "latitude": 401827388, - "longitude": -740294537 - }, - "name": "349 Sea Spray Court, Neptune City, NJ 07753, USA" - }, - { - "location": { - "latitude": 410564152, - "longitude": -743685054 - }, - "name": "13-17 Stanley Street, West Milford, NJ 07480, USA" - }, - { - "location": { - "latitude": 408472324, - "longitude": -740726046 - }, - "name": "47 Industrial Avenue, Teterboro, NJ 07608, USA" - }, - { - "location": { - "latitude": 412452168, - "longitude": -740214052 - }, - "name": "5 White Oak Lane, Stony Point, NY 10980, USA" - }, - { - "location": { - "latitude": 409146138, - "longitude": -746188906 - }, - "name": "Berkshire Valley Management Area Trail, Jefferson, NJ, USA" - }, - { - "location": { - "latitude": 404701380, - "longitude": -744781745 - }, - "name": "1007 Jersey Avenue, New Brunswick, NJ 08901, USA" - }, - { - "location": { - "latitude": 409642566, - "longitude": -746017679 - }, - "name": "6 East Emerald Isle Drive, Lake Hopatcong, NJ 07849, USA" - }, - { - "location": { - "latitude": 408031728, - "longitude": -748645385 - }, - "name": "1358-1474 New Jersey 57, Port Murray, NJ 07865, USA" - }, - { - "location": { - "latitude": 413700272, - "longitude": -742135189 - }, - "name": "367 Prospect Road, Chester, NY 10918, USA" - }, - { - "location": { - "latitude": 404310607, - "longitude": -740282632 - }, - "name": "10 Simon Lake Drive, Atlantic Highlands, NJ 07716, USA" - }, - { - "location": { - "latitude": 409319800, - "longitude": -746201391 - }, - "name": "11 Ward Street, Mount Arlington, NJ 07856, USA" - }, - { - "location": { - "latitude": 406685311, - "longitude": -742108603 - }, - "name": "300-398 Jefferson Avenue, Elizabeth, NJ 07201, USA" - }, - { - "location": { - "latitude": 419018117, - "longitude": -749142781 - }, - "name": "43 Dreher Road, Roscoe, NY 12776, USA" - }, - { - "location": { - "latitude": 412856162, - "longitude": -745148837 - }, - "name": "Swan Street, Pine Island, NY 10969, USA" - }, - { - "location": { - "latitude": 416560744, - "longitude": -746721964 - }, - "name": "66 Pleasantview Avenue, Monticello, NY 12701, USA" - }, - { - "location": { - "latitude": 405314270, - "longitude": -749836354 - }, - "name": "" - }, - { - "location": { - "latitude": 414219548, - "longitude": -743327440 - }, - "name": "" - }, - { - "location": { - "latitude": 415534177, - "longitude": -742900616 - }, - "name": "565 Winding Hills Road, Montgomery, NY 12549, USA" - }, - { - "location": { - "latitude": 406898530, - "longitude": -749127080 - }, - "name": "231 Rocky Run Road, Glen Gardner, NJ 08826, USA" - }, - { - "location": { - "latitude": 407586880, - "longitude": -741670168 - }, - "name": "100 Mount Pleasant Avenue, Newark, NJ 07104, USA" - }, - { - "location": { - "latitude": 400106455, - "longitude": -742870190 - }, - "name": "517-521 Huntington Drive, Manchester Township, NJ 08759, USA" - }, - { - "location": { - "latitude": 400066188, - "longitude": -746793294 - }, - "name": "" - }, - { - "location": { - "latitude": 418803880, - "longitude": -744102673 - }, - "name": "40 Mountain Road, Napanoch, NY 12458, USA" - }, - { - "location": { - "latitude": 414204288, - "longitude": -747895140 - }, - "name": "" - }, - { - "location": { - "latitude": 414777405, - "longitude": -740615601 - }, - "name": "" - }, - { - "location": { - "latitude": 415464475, - "longitude": -747175374 - }, - "name": "48 North Road, Forestburgh, NY 12777, USA" - }, - { - "location": { - "latitude": 404062378, - "longitude": -746376177 - }, - "name": "" - }, - { - "location": { - "latitude": 405688272, - "longitude": -749285130 - }, - "name": "" - }, - { - "location": { - "latitude": 400342070, - "longitude": -748788996 - }, - "name": "" - }, - { - "location": { - "latitude": 401809022, - "longitude": -744157964 - }, - "name": "" - }, - { - "location": { - "latitude": 404226644, - "longitude": -740517141 - }, - "name": "9 Thompson Avenue, Leonardo, NJ 07737, USA" - }, - { - "location": { - "latitude": 410322033, - "longitude": -747871659 - }, - "name": "" - }, - { - "location": { - "latitude": 407100674, - "longitude": -747742727 - }, - "name": "" - }, - { - "location": { - "latitude": 418811433, - "longitude": -741718005 - }, - "name": "213 Bush Road, Stone Ridge, NY 12484, USA" - }, - { - "location": { - "latitude": 415034302, - "longitude": -743850945 - }, - "name": "" - }, - { - "location": { - "latitude": 411349992, - "longitude": -743694161 - }, - "name": "" - }, - { - "location": { - "latitude": 404839914, - "longitude": -744759616 - }, - "name": "1-17 Bergen Court, New Brunswick, NJ 08901, USA" - }, - { - "location": { - "latitude": 414638017, - "longitude": -745957854 - }, - "name": "35 Oakland Valley Road, Cuddebackville, NY 12729, USA" - }, - { - "location": { - "latitude": 412127800, - "longitude": -740173578 - }, - "name": "" - }, - { - "location": { - "latitude": 401263460, - "longitude": -747964303 - }, - "name": "" - }, - { - "location": { - "latitude": 412843391, - "longitude": -749086026 - }, - "name": "" - }, - { - "location": { - "latitude": 418512773, - "longitude": -743067823 - }, - "name": "" - }, - { - "location": { - "latitude": 404318328, - "longitude": -740835638 - }, - "name": "42-102 Main Street, Belford, NJ 07718, USA" - }, - { - "location": { - "latitude": 419020746, - "longitude": -741172328 - }, - "name": "" - }, - { - "location": { - "latitude": 404080723, - "longitude": -746119569 - }, - "name": "" - }, - { - "location": { - "latitude": 401012643, - "longitude": -744035134 - }, - "name": "" - }, - { - "location": { - "latitude": 404306372, - "longitude": -741079661 - }, - "name": "" - }, - { - "location": { - "latitude": 403966326, - "longitude": -748519297 - }, - "name": "" - }, - { - "location": { - "latitude": 405002031, - "longitude": -748407866 - }, - "name": "" - }, - { - "location": { - "latitude": 409532885, - "longitude": -742200683 - }, - "name": "" - }, - { - "location": { - "latitude": 416851321, - "longitude": -742674555 - }, - "name": "" - }, - { - "location": { - "latitude": 406411633, - "longitude": -741722051 - }, - "name": "3387 Richmond Terrace, Staten Island, NY 10303, USA" - }, - { - "location": { - "latitude": 413069058, - "longitude": -744597778 - }, - "name": "261 Van Sickle Road, Goshen, NY 10924, USA" - }, - { - "location": { - "latitude": 418465462, - "longitude": -746859398 - }, - "name": "" - }, - { - "location": { - "latitude": 411733222, - "longitude": -744228360 - }, - "name": "" - }, - { - "location": { - "latitude": 410248224, - "longitude": -747127767 - }, - "name": "3 Hasta Way, Newton, NJ 07860, USA" - } -] diff --git a/Package@swift-6.swift b/Package@swift-6.swift deleted file mode 100644 index f8922e440..000000000 --- a/Package@swift-6.swift +++ /dev/null @@ -1,1127 +0,0 @@ -// swift-tools-version:6.0 -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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 PackageDescription -// swiftformat puts the next import before the tools version. -// swiftformat:disable:next sortImports -import class Foundation.ProcessInfo - -let grpcPackageName = "grpc-swift" -let grpcProductName = "GRPC" -let cgrpcZlibProductName = "CGRPCZlib" -let grpcTargetName = grpcProductName -let cgrpcZlibTargetName = cgrpcZlibProductName - -let includeNIOSSL = ProcessInfo.processInfo.environment["GRPC_NO_NIO_SSL"] == nil - -// MARK: - Package Dependencies - -let packageDependencies: [Package.Dependency] = [ - .package( - url: "https://github.com/apple/swift-nio.git", - from: "2.65.0" - ), - .package( - url: "https://github.com/apple/swift-nio-http2.git", - from: "1.32.0" - ), - .package( - url: "https://github.com/apple/swift-nio-transport-services.git", - from: "1.15.0" - ), - .package( - url: "https://github.com/apple/swift-nio-extras.git", - from: "1.4.0" - ), - .package( - url: "https://github.com/apple/swift-collections.git", - from: "1.0.5" - ), - .package( - url: "https://github.com/apple/swift-atomics.git", - from: "1.2.0" - ), - .package( - url: "https://github.com/apple/swift-protobuf.git", - from: "1.28.1" - ), - .package( - url: "https://github.com/apple/swift-log.git", - from: "1.4.4" - ), - .package( - url: "https://github.com/apple/swift-argument-parser.git", - // Version is higher than in other Package@swift manifests: 1.1.0 raised the minimum Swift - // version and indluded async support. - from: "1.1.1" - ), - .package( - url: "https://github.com/apple/swift-distributed-tracing.git", - from: "1.0.0" - ), -].appending( - .package( - url: "https://github.com/apple/swift-nio-ssl.git", - from: "2.23.0" - ), - if: includeNIOSSL -) - -// MARK: - Target Dependencies - -extension Target.Dependency { - // Target dependencies; external - static var grpc: Self { .target(name: grpcTargetName) } - static var cgrpcZlib: Self { .target(name: cgrpcZlibTargetName) } - static var protocGenGRPCSwift: Self { .target(name: "protoc-gen-grpc-swift") } - static var performanceWorker: Self { .target(name: "performance-worker") } - static var reflectionService: Self { .target(name: "GRPCReflectionService") } - static var grpcCodeGen: Self { .target(name: "GRPCCodeGen") } - static var grpcProtobuf: Self { .target(name: "GRPCProtobuf") } - static var grpcProtobufCodeGen: Self { .target(name: "GRPCProtobufCodeGen") } - - // Target dependencies; internal - static var grpcSampleData: Self { .target(name: "GRPCSampleData") } - static var echoModel: Self { .target(name: "EchoModel") } - static var echoImplementation: Self { .target(name: "EchoImplementation") } - static var helloWorldModel: Self { .target(name: "HelloWorldModel") } - static var routeGuideModel: Self { .target(name: "RouteGuideModel") } - static var interopTestModels: Self { .target(name: "GRPCInteroperabilityTestModels") } - static var interopTestImplementation: Self { - .target(name: "GRPCInteroperabilityTestsImplementation") - } - static var interoperabilityTests: Self { .target(name: "InteroperabilityTests") } - - // Product dependencies - static var argumentParser: Self { - .product( - name: "ArgumentParser", - package: "swift-argument-parser" - ) - } - static var nio: Self { .product(name: "NIO", package: "swift-nio") } - static var nioConcurrencyHelpers: Self { - .product( - name: "NIOConcurrencyHelpers", - package: "swift-nio" - ) - } - static var nioCore: Self { .product(name: "NIOCore", package: "swift-nio") } - static var nioEmbedded: Self { .product(name: "NIOEmbedded", package: "swift-nio") } - static var nioExtras: Self { .product(name: "NIOExtras", package: "swift-nio-extras") } - static var nioFoundationCompat: Self { .product(name: "NIOFoundationCompat", package: "swift-nio") } - static var nioHTTP1: Self { .product(name: "NIOHTTP1", package: "swift-nio") } - static var nioHTTP2: Self { .product(name: "NIOHTTP2", package: "swift-nio-http2") } - static var nioPosix: Self { .product(name: "NIOPosix", package: "swift-nio") } - static var nioSSL: Self { .product(name: "NIOSSL", package: "swift-nio-ssl") } - static var nioTLS: Self { .product(name: "NIOTLS", package: "swift-nio") } - static var nioTransportServices: Self { - .product( - name: "NIOTransportServices", - package: "swift-nio-transport-services" - ) - } - static var nioTestUtils: Self { .product(name: "NIOTestUtils", package: "swift-nio") } - static var nioFileSystem: Self { .product(name: "_NIOFileSystem", package: "swift-nio") } - static var logging: Self { .product(name: "Logging", package: "swift-log") } - static var protobuf: Self { .product(name: "SwiftProtobuf", package: "swift-protobuf") } - static var protobufPluginLibrary: Self { - .product( - name: "SwiftProtobufPluginLibrary", - package: "swift-protobuf" - ) - } - static var dequeModule: Self { .product(name: "DequeModule", package: "swift-collections") } - static var atomics: Self { .product(name: "Atomics", package: "swift-atomics") } - static var tracing: Self { .product(name: "Tracing", package: "swift-distributed-tracing") } - - static var grpcCore: Self { .target(name: "GRPCCore") } - static var grpcInProcessTransport: Self { .target(name: "GRPCInProcessTransport") } - static var grpcInterceptors: Self { .target(name: "GRPCInterceptors") } - static var grpcHTTP2Core: Self { .target(name: "GRPCHTTP2Core") } - static var grpcHTTP2Transport: Self { .target(name: "GRPCHTTP2Transport") } - static var grpcHTTP2TransportNIOPosix: Self { .target(name: "GRPCHTTP2TransportNIOPosix") } - static var grpcHTTP2TransportNIOTransportServices: Self { .target(name: "GRPCHTTP2TransportNIOTransportServices") } - static var grpcHealth: Self { .target(name: "GRPCHealth") } -} - -// MARK: - Targets - -extension Target { - static var grpc: Target { - .target( - name: grpcTargetName, - dependencies: [ - .cgrpcZlib, - .nio, - .nioCore, - .nioPosix, - .nioEmbedded, - .nioFoundationCompat, - .nioTLS, - .nioTransportServices, - .nioHTTP1, - .nioHTTP2, - .nioExtras, - .logging, - .protobuf, - .dequeModule, - .atomics - ].appending( - .nioSSL, if: includeNIOSSL - ), - path: "Sources/GRPC", - swiftSettings: [.swiftLanguageMode(.v5)] - ) - } - - static var grpcCore: Target { - .target( - name: "GRPCCore", - dependencies: [ - .dequeModule, - ], - path: "Sources/GRPCCore", - swiftSettings: [ - .swiftLanguageMode(.v6), - .enableUpcomingFeature("ExistentialAny"), - .enableUpcomingFeature("InternalImportsByDefault") - ] - ) - } - - static var grpcInProcessTransport: Target { - .target( - name: "GRPCInProcessTransport", - dependencies: [ - .grpcCore - ], - swiftSettings: [ - .swiftLanguageMode(.v6), - .enableUpcomingFeature("ExistentialAny"), - .enableUpcomingFeature("InternalImportsByDefault") - ] - ) - } - - static var grpcInterceptors: Target { - .target( - name: "GRPCInterceptors", - dependencies: [ - .grpcCore, - .tracing - ], - swiftSettings: [ - .swiftLanguageMode(.v6), - .enableUpcomingFeature("ExistentialAny"), - .enableUpcomingFeature("InternalImportsByDefault") - ] - ) - } - - static var grpcHTTP2Core: Target { - .target( - name: "GRPCHTTP2Core", - dependencies: [ - .grpcCore, - .nioCore, - .nioHTTP2, - .nioTLS, - .nioExtras, - .cgrpcZlib, - .dequeModule, - ], - swiftSettings: [ - .swiftLanguageMode(.v6), - .enableUpcomingFeature("ExistentialAny"), - .enableUpcomingFeature("InternalImportsByDefault") - ] - ) - } - - static var grpcHTTP2TransportNIOPosix: Target { - .target( - name: "GRPCHTTP2TransportNIOPosix", - dependencies: [ - .grpcCore, - .grpcHTTP2Core, - .nioPosix, - .nioExtras - ].appending( - .nioSSL, - if: includeNIOSSL - ), - swiftSettings: [ - .swiftLanguageMode(.v6), - .enableUpcomingFeature("ExistentialAny"), - .enableUpcomingFeature("InternalImportsByDefault") - ] - ) - } - - static var grpcHTTP2TransportNIOTransportServices: Target { - .target( - name: "GRPCHTTP2TransportNIOTransportServices", - dependencies: [ - .grpcCore, - .grpcHTTP2Core, - .nioCore, - .nioExtras, - .nioTransportServices - ], - swiftSettings: [ - .swiftLanguageMode(.v6), - .enableUpcomingFeature("ExistentialAny"), - .enableUpcomingFeature("InternalImportsByDefault") - ] - ) - } - - static var grpcHTTP2Transport: Target { - .target( - name: "GRPCHTTP2Transport", - dependencies: [ - .grpcCore, - .grpcHTTP2Core, - .grpcHTTP2TransportNIOPosix, - .grpcHTTP2TransportNIOTransportServices, - ], - swiftSettings: [ - .swiftLanguageMode(.v6), - .enableUpcomingFeature("ExistentialAny"), - .enableUpcomingFeature("InternalImportsByDefault") - ] - ) - } - - static var cgrpcZlib: Target { - .target( - name: cgrpcZlibTargetName, - path: "Sources/CGRPCZlib", - linkerSettings: [ - .linkedLibrary("z"), - ] - ) - } - - static var protocGenGRPCSwift: Target { - .executableTarget( - name: "protoc-gen-grpc-swift", - dependencies: [ - .protobuf, - .protobufPluginLibrary, - .grpcCodeGen, - .grpcProtobufCodeGen - ], - exclude: [ - "README.md", - ], - swiftSettings: [.swiftLanguageMode(.v5)] - ) - } - - static var performanceWorker: Target { - .executableTarget( - name: "performance-worker", - dependencies: [ - .grpcCore, - .grpcHTTP2Core, - .grpcHTTP2TransportNIOPosix, - .grpcProtobuf, - .nioCore, - .nioFileSystem, - .argumentParser - ], - swiftSettings: [ - .swiftLanguageMode(.v6), - .enableUpcomingFeature("ExistentialAny") - ] - ) - } - - static var grpcSwiftPlugin: Target { - .plugin( - name: "GRPCSwiftPlugin", - capability: .buildTool(), - dependencies: [ - .protocGenGRPCSwift, - ] - ) - } - - static var grpcTests: Target { - .testTarget( - name: "GRPCTests", - dependencies: [ - .grpc, - .echoModel, - .echoImplementation, - .helloWorldModel, - .interopTestModels, - .interopTestImplementation, - .grpcSampleData, - .nioCore, - .nioConcurrencyHelpers, - .nioPosix, - .nioTLS, - .nioHTTP1, - .nioHTTP2, - .nioEmbedded, - .nioTransportServices, - .logging, - .reflectionService, - .atomics - ].appending( - .nioSSL, if: includeNIOSSL - ), - exclude: [ - "Codegen/Serialization/echo.grpc.reflection" - ], - swiftSettings: [.swiftLanguageMode(.v5)] - ) - } - - static var grpcCoreTests: Target { - .testTarget( - name: "GRPCCoreTests", - dependencies: [ - .grpcCore, - .grpcInProcessTransport, - .dequeModule, - .protobuf, - ], - resources: [ - .copy("Configuration/Inputs") - ], - swiftSettings: [.swiftLanguageMode(.v6), .enableUpcomingFeature("ExistentialAny")] - ) - } - - static var grpcInProcessTransportTests: Target { - .testTarget( - name: "GRPCInProcessTransportTests", - dependencies: [ - .grpcCore, - .grpcInProcessTransport - ], - swiftSettings: [.swiftLanguageMode(.v6), .enableUpcomingFeature("ExistentialAny")] - ) - } - - static var grpcInterceptorsTests: Target { - .testTarget( - name: "GRPCInterceptorsTests", - dependencies: [ - .grpcCore, - .tracing, - .nioCore, - .grpcInterceptors - ], - swiftSettings: [.swiftLanguageMode(.v6), .enableUpcomingFeature("ExistentialAny")] - ) - } - - static var grpcHTTP2CoreTests: Target { - .testTarget( - name: "GRPCHTTP2CoreTests", - dependencies: [ - .grpcHTTP2Core, - .nioCore, - .nioHTTP2, - .nioEmbedded, - .nioTestUtils, - ], - swiftSettings: [.swiftLanguageMode(.v6), .enableUpcomingFeature("ExistentialAny")] - ) - } - - static var grpcHTTP2TransportTests: Target { - .testTarget( - name: "GRPCHTTP2TransportTests", - dependencies: [ - .grpcHTTP2Core, - .grpcHTTP2TransportNIOPosix, - .grpcHTTP2TransportNIOTransportServices, - .grpcProtobuf - ].appending( - .nioSSL, if: includeNIOSSL - ), - swiftSettings: [.swiftLanguageMode(.v6), .enableUpcomingFeature("ExistentialAny")] - ) - } - - static var grpcCodeGenTests: Target { - .testTarget( - name: "GRPCCodeGenTests", - dependencies: [ - .grpcCodeGen - ], - swiftSettings: [.swiftLanguageMode(.v6), .enableUpcomingFeature("ExistentialAny")] - ) - } - - static var grpcProtobufTests: Target { - .testTarget( - name: "GRPCProtobufTests", - dependencies: [ - .grpcProtobuf, - .grpcCore, - .protobuf - ], - swiftSettings: [.swiftLanguageMode(.v6), .enableUpcomingFeature("ExistentialAny")] - ) - } - - static var grpcProtobufCodeGenTests: Target { - .testTarget( - name: "GRPCProtobufCodeGenTests", - dependencies: [ - .grpcCodeGen, - .grpcProtobufCodeGen, - .protobuf, - .protobufPluginLibrary - ], - swiftSettings: [.swiftLanguageMode(.v6), .enableUpcomingFeature("ExistentialAny")] - ) - } - - static var inProcessInteroperabilityTests: Target { - .testTarget( - name: "InProcessInteroperabilityTests", - dependencies: [ - .grpcInProcessTransport, - .interoperabilityTests, - .grpcCore - ], - swiftSettings: [.swiftLanguageMode(.v6), .enableUpcomingFeature("ExistentialAny")] - ) - } - - static var grpcHealthTests: Target { - .testTarget( - name: "GRPCHealthTests", - dependencies: [ - .grpcHealth, - .grpcProtobuf, - .grpcInProcessTransport - ], - path: "Tests/Services/HealthTests", - swiftSettings: [.swiftLanguageMode(.v6), .enableUpcomingFeature("ExistentialAny")] - ) - } - - static var interopTestModels: Target { - .target( - name: "GRPCInteroperabilityTestModels", - dependencies: [ - .grpc, - .nio, - .protobuf, - ], - exclude: [ - "README.md", - "generate.sh", - "src/proto/grpc/testing/empty.proto", - "src/proto/grpc/testing/empty_service.proto", - "src/proto/grpc/testing/messages.proto", - "src/proto/grpc/testing/test.proto", - "unimplemented_call.patch", - ], - swiftSettings: [.swiftLanguageMode(.v5)] - ) - } - - static var interoperabilityTestImplementation: Target { - .target( - name: "InteroperabilityTests", - dependencies: [ - .grpcCore, - .grpcProtobuf - ], - swiftSettings: [ - .swiftLanguageMode(.v6), - .enableUpcomingFeature("ExistentialAny"), - .enableUpcomingFeature("InternalImportsByDefault") - ] - ) - } - - static var interoperabilityTestsExecutable: Target { - .executableTarget( - name: "interoperability-tests", - dependencies: [ - .grpcCore, - .grpcHTTP2Core, - .grpcHTTP2TransportNIOPosix, - .interoperabilityTests, - .argumentParser - ], - swiftSettings: [.swiftLanguageMode(.v6), .enableUpcomingFeature("ExistentialAny")] - ) - } - - static var interopTestImplementation: Target { - .target( - name: "GRPCInteroperabilityTestsImplementation", - dependencies: [ - .grpc, - .interopTestModels, - .nioCore, - .nioPosix, - .nioHTTP1, - .logging, - ].appending( - .nioSSL, if: includeNIOSSL - ), - swiftSettings: [.swiftLanguageMode(.v5)] - ) - } - - static var interopTests: Target { - .executableTarget( - name: "GRPCInteroperabilityTests", - dependencies: [ - .grpc, - .interopTestImplementation, - .nioCore, - .nioPosix, - .logging, - .argumentParser, - ], - swiftSettings: [.swiftLanguageMode(.v5)] - ) - } - - static var backoffInteropTest: Target { - .executableTarget( - name: "GRPCConnectionBackoffInteropTest", - dependencies: [ - .grpc, - .interopTestModels, - .nioCore, - .nioPosix, - .logging, - .argumentParser, - ], - exclude: [ - "README.md", - ], - swiftSettings: [.swiftLanguageMode(.v5)] - ) - } - - static var perfTests: Target { - .executableTarget( - name: "GRPCPerformanceTests", - dependencies: [ - .grpc, - .grpcSampleData, - .nioCore, - .nioEmbedded, - .nioPosix, - .nioHTTP2, - .argumentParser, - ], - swiftSettings: [.swiftLanguageMode(.v5)] - ) - } - - static var grpcSampleData: Target { - .target( - name: "GRPCSampleData", - dependencies: includeNIOSSL ? [.nioSSL] : [], - exclude: [ - "bundle.p12", - ], - swiftSettings: [.swiftLanguageMode(.v5)] - ) - } - - static var echoModel: Target { - .target( - name: "EchoModel", - dependencies: [ - .grpc, - .nio, - .protobuf, - ], - path: "Examples/v1/Echo/Model", - swiftSettings: [.swiftLanguageMode(.v5)] - ) - } - - static var echoImplementation: Target { - .target( - name: "EchoImplementation", - dependencies: [ - .echoModel, - .grpc, - .nioCore, - .nioHTTP2, - .protobuf, - ], - path: "Examples/v1/Echo/Implementation", - swiftSettings: [.swiftLanguageMode(.v5)] - ) - } - - static var echo: Target { - .executableTarget( - name: "Echo", - dependencies: [ - .grpc, - .echoModel, - .echoImplementation, - .grpcSampleData, - .nioCore, - .nioPosix, - .logging, - .argumentParser, - ].appending( - .nioSSL, if: includeNIOSSL - ), - path: "Examples/v1/Echo/Runtime", - swiftSettings: [.swiftLanguageMode(.v5)] - ) - } - - static var echo_v2: Target { - .executableTarget( - name: "echo-v2", - dependencies: [ - .grpcCore, - .grpcProtobuf, - .grpcHTTP2Core, - .grpcHTTP2TransportNIOPosix, - .argumentParser, - ].appending( - .nioSSL, if: includeNIOSSL - ), - path: "Examples/v2/echo", - swiftSettings: [ - .swiftLanguageMode(.v6), - .enableUpcomingFeature("ExistentialAny") - ] - ) - } - - static var helloWorldModel: Target { - .target( - name: "HelloWorldModel", - dependencies: [ - .grpc, - .nio, - .protobuf, - ], - path: "Examples/v1/HelloWorld/Model", - swiftSettings: [.swiftLanguageMode(.v5)] - ) - } - - static var helloWorldClient: Target { - .executableTarget( - name: "HelloWorldClient", - dependencies: [ - .grpc, - .helloWorldModel, - .nioCore, - .nioPosix, - .argumentParser, - ], - path: "Examples/v1/HelloWorld/Client", - swiftSettings: [.swiftLanguageMode(.v5)] - ) - } - - static var helloWorldServer: Target { - .executableTarget( - name: "HelloWorldServer", - dependencies: [ - .grpc, - .helloWorldModel, - .nioCore, - .nioPosix, - .argumentParser, - ], - path: "Examples/v1/HelloWorld/Server", - swiftSettings: [.swiftLanguageMode(.v5)] - ) - } - - static var helloWorld_v2: Target { - .executableTarget( - name: "hello-world", - dependencies: [ - .grpcProtobuf, - .grpcHTTP2Transport, - .argumentParser, - ], - path: "Examples/v2/hello-world", - exclude: [ - "HelloWorld.proto" - ], - swiftSettings: [ - .swiftLanguageMode(.v6), - .enableUpcomingFeature("ExistentialAny") - ] - ) - } - - static var routeGuideModel: Target { - .target( - name: "RouteGuideModel", - dependencies: [ - .grpc, - .nio, - .protobuf, - ], - path: "Examples/v1/RouteGuide/Model", - swiftSettings: [.swiftLanguageMode(.v5)] - ) - } - - static var routeGuideClient: Target { - .executableTarget( - name: "RouteGuideClient", - dependencies: [ - .grpc, - .routeGuideModel, - .nioCore, - .nioPosix, - .argumentParser, - ], - path: "Examples/v1/RouteGuide/Client", - swiftSettings: [.swiftLanguageMode(.v5)] - ) - } - - static var routeGuideServer: Target { - .executableTarget( - name: "RouteGuideServer", - dependencies: [ - .grpc, - .routeGuideModel, - .nioCore, - .nioConcurrencyHelpers, - .nioPosix, - .argumentParser, - ], - path: "Examples/v1/RouteGuide/Server", - swiftSettings: [.swiftLanguageMode(.v5)] - ) - } - - static var routeGuide_v2: Target { - .executableTarget( - name: "route-guide", - dependencies: [ - .grpcProtobuf, - .grpcHTTP2Transport, - .argumentParser, - ], - path: "Examples/v2/route-guide", - resources: [ - .copy("route_guide_db.json") - ], - swiftSettings: [ - .swiftLanguageMode(.v6), - .enableUpcomingFeature("ExistentialAny") - ] - ) - } - - static var packetCapture: Target { - .executableTarget( - name: "PacketCapture", - dependencies: [ - .grpc, - .echoModel, - .nioCore, - .nioPosix, - .nioExtras, - .argumentParser, - ], - path: "Examples/v1/PacketCapture", - exclude: [ - "README.md", - ], - swiftSettings: [.swiftLanguageMode(.v5)] - ) - } - - static var reflectionService: Target { - .target( - name: "GRPCReflectionService", - dependencies: [ - .grpc, - .nio, - .protobuf, - ], - path: "Sources/GRPCReflectionService", - swiftSettings: [.swiftLanguageMode(.v5)] - ) - } - - static var reflectionServer: Target { - .executableTarget( - name: "ReflectionServer", - dependencies: [ - .grpc, - .reflectionService, - .helloWorldModel, - .nioCore, - .nioPosix, - .argumentParser, - .echoModel, - .echoImplementation - ], - path: "Examples/v1/ReflectionService", - resources: [ - .copy("Generated") - ], - swiftSettings: [.swiftLanguageMode(.v5)] - ) - } - - static var grpcCodeGen: Target { - .target( - name: "GRPCCodeGen", - path: "Sources/GRPCCodeGen", - swiftSettings: [ - .swiftLanguageMode(.v6), - .enableUpcomingFeature("ExistentialAny"), - .enableUpcomingFeature("InternalImportsByDefault") - ] - ) - } - - static var grpcProtobuf: Target { - .target( - name: "GRPCProtobuf", - dependencies: [ - .grpcCore, - .protobuf, - ], - path: "Sources/GRPCProtobuf", - swiftSettings: [ - .swiftLanguageMode(.v6), - .enableUpcomingFeature("ExistentialAny"), - .enableUpcomingFeature("InternalImportsByDefault") - ] - ) - } - - static var grpcProtobufCodeGen: Target { - .target( - name: "GRPCProtobufCodeGen", - dependencies: [ - .protobuf, - .protobufPluginLibrary, - .grpcCodeGen - ], - path: "Sources/GRPCProtobufCodeGen", - swiftSettings: [ - .swiftLanguageMode(.v6), - .enableUpcomingFeature("ExistentialAny"), - .enableUpcomingFeature("InternalImportsByDefault") - ] - ) - } - - static var grpcHealth: Target { - .target( - name: "GRPCHealth", - dependencies: [ - .grpcCore, - .grpcProtobuf - ], - path: "Sources/Services/Health", - swiftSettings: [ - .swiftLanguageMode(.v6), - .enableUpcomingFeature("ExistentialAny"), - .enableUpcomingFeature("InternalImportsByDefault") - ] - ) - } -} - -// MARK: - Products - -extension Product { - static var grpc: Product { - .library( - name: grpcProductName, - targets: [grpcTargetName] - ) - } - - static var _grpcCore: Product { - .library( - name: "_GRPCCore", - targets: ["GRPCCore"] - ) - } - - static var _grpcProtobuf: Product { - .library( - name: "_GRPCProtobuf", - targets: ["GRPCProtobuf"] - ) - } - - static var _grpcInProcessTransport: Product { - .library( - name: "_GRPCInProcessTransport", - targets: ["GRPCInProcessTransport"] - ) - } - - static var _grpcHTTP2Transport: Product { - .library( - name: "_GRPCHTTP2Transport", - targets: ["GRPCHTTP2Transport"] - ) - } - - static var cgrpcZlib: Product { - .library( - name: cgrpcZlibProductName, - targets: [cgrpcZlibTargetName] - ) - } - - static var grpcReflectionService: Product { - .library( - name: "GRPCReflectionService", - targets: ["GRPCReflectionService"] - ) - } - - static var protocGenGRPCSwift: Product { - .executable( - name: "protoc-gen-grpc-swift", - targets: ["protoc-gen-grpc-swift"] - ) - } - - static var grpcSwiftPlugin: Product { - .plugin( - name: "GRPCSwiftPlugin", - targets: ["GRPCSwiftPlugin"] - ) - } -} - -// MARK: - Package - -let package = Package( - name: grpcPackageName, - products: [ - // v1 - .grpc, - .cgrpcZlib, - .grpcReflectionService, - .protocGenGRPCSwift, - .grpcSwiftPlugin, - // v2 - ._grpcCore, - ._grpcProtobuf, - ._grpcHTTP2Transport, - ._grpcInProcessTransport, - ], - dependencies: packageDependencies, - targets: [ - // Products - .grpc, - .cgrpcZlib, - .protocGenGRPCSwift, - .grpcSwiftPlugin, - .reflectionService, - - // Tests etc. - .grpcTests, - .interopTestModels, - .interopTestImplementation, - .interopTests, - .backoffInteropTest, - .perfTests, - .grpcSampleData, - - // Examples - .echoModel, - .echoImplementation, - .echo, - .helloWorldModel, - .helloWorldClient, - .helloWorldServer, - .routeGuideModel, - .routeGuideClient, - .routeGuideServer, - .packetCapture, - .reflectionServer, - - // v2 - .grpcCore, - .grpcCodeGen, - - // v2 transports - .grpcInProcessTransport, - .grpcHTTP2Core, - .grpcHTTP2TransportNIOPosix, - .grpcHTTP2TransportNIOTransportServices, - .grpcHTTP2Transport, - - // v2 Protobuf support - .grpcProtobuf, - .grpcProtobufCodeGen, - - // v2 add-ons - .grpcInterceptors, - .grpcHealth, - - // v2 integration testing - .interoperabilityTestImplementation, - .interoperabilityTestsExecutable, - .performanceWorker, - - // v2 unit tests - .grpcCoreTests, - .grpcInProcessTransportTests, - .grpcCodeGenTests, - .grpcInterceptorsTests, - .grpcHTTP2CoreTests, - .grpcHTTP2TransportTests, - .grpcHealthTests, - .grpcProtobufTests, - .grpcProtobufCodeGenTests, - .inProcessInteroperabilityTests, - - // v2 examples - .echo_v2, - .helloWorld_v2, - .routeGuide_v2, - ] -) - -extension Array { - func appending(_ element: Element, if condition: Bool) -> [Element] { - if condition { - return self + [element] - } else { - return self - } - } -} diff --git a/Plugins/GRPCSwiftPlugin/plugin.swift b/Plugins/GRPCSwiftPlugin/plugin.swift index 2c773e79c..e310de73d 100644 --- a/Plugins/GRPCSwiftPlugin/plugin.swift +++ b/Plugins/GRPCSwiftPlugin/plugin.swift @@ -261,11 +261,7 @@ extension GRPCSwiftPlugin: BuildToolPlugin { throw PluginError.invalidTarget("\(type(of: target))") } - #if compiler(<6.0) let workDirectory = PathLike(context.pluginWorkDirectory) - #else - let workDirectory = PathLike(context.pluginWorkDirectoryURL) - #endif return try self.createBuildCommands( pluginWorkDirectory: workDirectory, @@ -279,11 +275,7 @@ extension GRPCSwiftPlugin: BuildToolPlugin { // methods, properties, and conformances have been deprecated but the type hasn't.) This type wraps // either depending on the compiler version. struct PathLike: CustomStringConvertible { - #if compiler(<6.0) typealias Value = Path - #else - typealias Value = URL - #endif private(set) var value: Value @@ -292,64 +284,34 @@ struct PathLike: CustomStringConvertible { } init(_ path: String) { - #if compiler(<6.0) self.value = Path(path) - #else - self.value = URL(fileURLWithPath: path) - #endif } init(_ element: FileList.Element) { - #if compiler(<6.0) self.value = element.path - #else - self.value = element.url - #endif } init(_ element: PluginContext.Tool) { - #if compiler(<6.0) self.value = element.path - #else - self.value = element.url - #endif } var description: String { - #if compiler(<6.0) return String(describing: self.value) - #elseif canImport(Darwin) - return self.value.path(percentEncoded: false) - #else - return self.value.path - #endif } var lastComponent: String { - #if compiler(<6.0) return self.value.lastComponent - #else - return self.value.lastPathComponent - #endif } func removingLastComponent() -> Self { var copy = self - #if compiler(<6.0) copy.value = self.value.removingLastComponent() - #else - copy.value = self.value.deletingLastPathComponent() - #endif return copy } func appending(_ path: String) -> Self { var copy = self - #if compiler(<6.0) copy.value = self.value.appending(path) - #else - copy.value = self.value.appendingPathComponent(path) - #endif return copy } } @@ -374,11 +336,7 @@ extension Command { extension URL { init(_ pathLike: PathLike) { - #if compiler(<6.0) self = URL(fileURLWithPath: "\(pathLike.value)") - #else - self = pathLike.value - #endif } } @@ -390,11 +348,7 @@ extension GRPCSwiftPlugin: XcodeBuildToolPlugin { context: XcodePluginContext, target: XcodeTarget ) throws -> [Command] { - #if compiler(<6.0) let workDirectory = PathLike(context.pluginWorkDirectory) - #else - let workDirectory = PathLike(context.pluginWorkDirectoryURL) - #endif return try self.createBuildCommands( pluginWorkDirectory: workDirectory, diff --git a/Sources/GRPCCodeGen/CodeGenError.swift b/Sources/GRPCCodeGen/CodeGenError.swift deleted file mode 100644 index 6cfffa32c..000000000 --- a/Sources/GRPCCodeGen/CodeGenError.swift +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -/// A error thrown by the ``SourceGenerator`` to signal errors in the ``CodeGenerationRequest`` object. -public struct CodeGenError: Error, Hashable, Sendable { - /// The code indicating the domain of the error. - public var code: Code - /// A message providing more details about the error which may include details specific to this - /// instance of the error. - public var message: String - - /// Creates a new error. - /// - /// - Parameters: - /// - code: The error code. - /// - message: A description of the error. - public init(code: Code, message: String) { - self.code = code - self.message = message - } -} - -extension CodeGenError { - public struct Code: Hashable, Sendable { - private enum Value { - case nonUniqueServiceName - case nonUniqueMethodName - case invalidKind - } - - private var value: Value - private init(_ value: Value) { - self.value = value - } - - /// The same name is used for two services that are either in the same namespace or don't have a namespace. - public static var nonUniqueServiceName: Self { - Self(.nonUniqueServiceName) - } - - /// The same name is used for two methods of the same service. - public static var nonUniqueMethodName: Self { - Self(.nonUniqueMethodName) - } - - /// An invalid kind name is used for an import. - public static var invalidKind: Self { - Self(.invalidKind) - } - } -} - -extension CodeGenError: CustomStringConvertible { - public var description: String { - return "\(self.code): \"\(self.message)\"" - } -} diff --git a/Sources/GRPCCodeGen/CodeGenerationRequest.swift b/Sources/GRPCCodeGen/CodeGenerationRequest.swift deleted file mode 100644 index 700f33866..000000000 --- a/Sources/GRPCCodeGen/CodeGenerationRequest.swift +++ /dev/null @@ -1,337 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -/// Describes the services, dependencies and trivia from an IDL file, -/// and the IDL itself through its specific serializer and deserializer. -public struct CodeGenerationRequest { - /// The name of the source file containing the IDL, including the extension if applicable. - public var fileName: String - - /// Any comments at the top of the file such as documentation and copyright headers. - /// They will be placed at the top of the generated file. They are already formatted, - /// meaning they contain "///" and new lines. - public var leadingTrivia: String - - /// The Swift imports that the generated file depends on. The gRPC specific imports aren't required - /// as they will be added by default in the generated file. - /// - /// - SeeAlso: ``Dependency``. - public var dependencies: [Dependency] - - /// A description of each service to generate. - /// - /// - SeeAlso: ``ServiceDescriptor``. - public var services: [ServiceDescriptor] - - /// Closure that receives a message type as a `String` and returns a code snippet to - /// initialise a `MessageSerializer` for that type as a `String`. - /// - /// The result is inserted in the generated code, where clients serialize RPC inputs and - /// servers serialize RPC outputs. - /// - /// For example, to serialize Protobuf messages you could specify a serializer as: - /// ```swift - /// request.lookupSerializer = { messageType in - /// "ProtobufSerializer<\(messageType)>()" - /// } - /// ``` - public var lookupSerializer: (_ messageType: String) -> String - - /// Closure that receives a message type as a `String` and returns a code snippet to - /// initialize a `MessageDeserializer` for that type as a `String`. - /// - /// The result is inserted in the generated code, where clients deserialize RPC outputs and - /// servers deserialize RPC inputs. - /// - /// For example, to serialize Protobuf messages you could specify a serializer as: - /// ```swift - /// request.lookupDeserializer = { messageType in - /// "ProtobufDeserializer<\(messageType)>()" - /// } - /// ``` - public var lookupDeserializer: (_ messageType: String) -> String - - public init( - fileName: String, - leadingTrivia: String, - dependencies: [Dependency], - services: [ServiceDescriptor], - lookupSerializer: @escaping (String) -> String, - lookupDeserializer: @escaping (String) -> String - ) { - self.fileName = fileName - self.leadingTrivia = leadingTrivia - self.dependencies = dependencies - self.services = services - self.lookupSerializer = lookupSerializer - self.lookupDeserializer = lookupDeserializer - } - - /// Represents an import: a module or a specific item from a module. - public struct Dependency: Equatable { - /// If the dependency is an item, the property's value is the item representation. - /// If the dependency is a module, this property is nil. - public var item: Item? - - /// The access level to be included in imports of this dependency. - public var accessLevel: SourceGenerator.Config.AccessLevel - - /// The name of the imported module or of the module an item is imported from. - public var module: String - - /// The name of the private interface for an `@_spi` import. - /// - /// For example, if `spi` was "Secret" and the module name was "Foo" then the import - /// would be `@_spi(Secret) import Foo`. - public var spi: String? - - /// Requirements for the `@preconcurrency` attribute. - public var preconcurrency: PreconcurrencyRequirement - - public init( - item: Item? = nil, - module: String, - spi: String? = nil, - preconcurrency: PreconcurrencyRequirement = .notRequired, - accessLevel: SourceGenerator.Config.AccessLevel - ) { - self.item = item - self.module = module - self.spi = spi - self.preconcurrency = preconcurrency - self.accessLevel = accessLevel - } - - /// Represents an item imported from a module. - public struct Item: Equatable { - /// The keyword that specifies the item's kind (e.g. `func`, `struct`). - public var kind: Kind - - /// The name of the imported item. - public var name: String - - public init(kind: Kind, name: String) { - self.kind = kind - self.name = name - } - - /// Represents the imported item's kind. - public struct Kind: Equatable { - /// Describes the keyword associated with the imported item. - internal enum Value: String { - case `typealias` - case `struct` - case `class` - case `enum` - case `protocol` - case `let` - case `var` - case `func` - } - - internal var value: Value - - internal init(_ value: Value) { - self.value = value - } - - /// The imported item is a typealias. - public static var `typealias`: Self { - Self(.`typealias`) - } - - /// The imported item is a struct. - public static var `struct`: Self { - Self(.`struct`) - } - - /// The imported item is a class. - public static var `class`: Self { - Self(.`class`) - } - - /// The imported item is an enum. - public static var `enum`: Self { - Self(.`enum`) - } - - /// The imported item is a protocol. - public static var `protocol`: Self { - Self(.`protocol`) - } - - /// The imported item is a let. - public static var `let`: Self { - Self(.`let`) - } - - /// The imported item is a var. - public static var `var`: Self { - Self(.`var`) - } - - /// The imported item is a function. - public static var `func`: Self { - Self(.`func`) - } - } - } - - /// Describes any requirement for the `@preconcurrency` attribute. - public struct PreconcurrencyRequirement: Equatable { - internal enum Value: Equatable { - case required - case notRequired - case requiredOnOS([String]) - } - - internal var value: Value - - internal init(_ value: Value) { - self.value = value - } - - /// The attribute is always required. - public static var required: Self { - Self(.required) - } - - /// The attribute is not required. - public static var notRequired: Self { - Self(.notRequired) - } - - /// The attribute is required only on the named operating systems. - public static func requiredOnOS(_ OSs: [String]) -> PreconcurrencyRequirement { - return Self(.requiredOnOS(OSs)) - } - } - } - - /// Represents a service described in an IDL file. - public struct ServiceDescriptor: Hashable { - /// Documentation from comments above the IDL service description. - /// It is already formatted, meaning it contains "///" and new lines. - public var documentation: String - - /// The service name in different formats. - /// - /// All properties of this object must be unique for each service from within a namespace. - public var name: Name - - /// The service namespace in different formats. - /// - /// All different services from within the same namespace must have - /// the same ``Name`` object as this property. - /// For `.proto` files the base name of this object is the package name. - public var namespace: Name - - /// A description of each method of a service. - /// - /// - SeeAlso: ``MethodDescriptor``. - public var methods: [MethodDescriptor] - - public init( - documentation: String, - name: Name, - namespace: Name, - methods: [MethodDescriptor] - ) { - self.documentation = documentation - self.name = name - self.namespace = namespace - self.methods = methods - } - - /// Represents a method described in an IDL file. - public struct MethodDescriptor: Hashable { - /// Documentation from comments above the IDL method description. - /// It is already formatted, meaning it contains "///" and new lines. - public var documentation: String - - /// Method name in different formats. - /// - /// All properties of this object must be unique for each method - /// from within a service. - public var name: Name - - /// Identifies if the method is input streaming. - public var isInputStreaming: Bool - - /// Identifies if the method is output streaming. - public var isOutputStreaming: Bool - - /// The generated input type for the described method. - public var inputType: String - - /// The generated output type for the described method. - public var outputType: String - - public init( - documentation: String, - name: Name, - isInputStreaming: Bool, - isOutputStreaming: Bool, - inputType: String, - outputType: String - ) { - self.documentation = documentation - self.name = name - self.isInputStreaming = isInputStreaming - self.isOutputStreaming = isOutputStreaming - self.inputType = inputType - self.outputType = outputType - } - } - } - - /// Represents the name associated with a namespace, service or a method, in three different formats. - public struct Name: Hashable { - /// The base name is the name used for the namespace/service/method in the IDL file, so it should follow - /// the specific casing of the IDL. - /// - /// The base name is also used in the descriptors that identify a specific method or service : - /// `..`. - public var base: String - - /// The `generatedUpperCase` name is used in the generated code. It is expected - /// to be the UpperCamelCase version of the base name - /// - /// For example, if `base` is "fooBar", then `generatedUpperCase` is "FooBar". - public var generatedUpperCase: String - - /// The `generatedLowerCase` name is used in the generated code. It is expected - /// to be the lowerCamelCase version of the base name - /// - /// For example, if `base` is "FooBar", then `generatedLowerCase` is "fooBar". - public var generatedLowerCase: String - - public init(base: String, generatedUpperCase: String, generatedLowerCase: String) { - self.base = base - self.generatedUpperCase = generatedUpperCase - self.generatedLowerCase = generatedLowerCase - } - } -} - -extension CodeGenerationRequest.Name { - /// The base name replacing occurrences of "." with "_". - /// - /// For example, if `base` is "Foo.Bar", then `normalizedBase` is "Foo_Bar". - public var normalizedBase: String { - return self.base.replacingOccurrences(of: ".", with: "_") - } -} diff --git a/Sources/GRPCCodeGen/Internal/Renderer/RendererProtocol.swift b/Sources/GRPCCodeGen/Internal/Renderer/RendererProtocol.swift deleted file mode 100644 index a08700e65..000000000 --- a/Sources/GRPCCodeGen/Internal/Renderer/RendererProtocol.swift +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ -//===----------------------------------------------------------------------===// -// -// This source file is part of the SwiftOpenAPIGenerator open source project -// -// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -/// An object that renders structured Swift representations -/// into Swift files. -/// -/// Rendering is the last phase of the generator pipeline. -protocol RendererProtocol { - - /// Renders the specified structured code into a raw Swift file. - /// - Parameters: - /// - code: A structured representation of the Swift code. - /// - config: The configuration of the generator. - /// - diagnostics: The collector to which to emit diagnostics. - /// - Returns: A raw file with Swift contents. - /// - Throws: An error if an issue occurs during rendering. - func render(structured code: StructuredSwiftRepresentation) throws -> SourceFile -} diff --git a/Sources/GRPCCodeGen/Internal/Renderer/TextBasedRenderer.swift b/Sources/GRPCCodeGen/Internal/Renderer/TextBasedRenderer.swift deleted file mode 100644 index fe7f038a6..000000000 --- a/Sources/GRPCCodeGen/Internal/Renderer/TextBasedRenderer.swift +++ /dev/null @@ -1,1189 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ -//===----------------------------------------------------------------------===// -// -// This source file is part of the SwiftOpenAPIGenerator open source project -// -// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// -import Foundation - -/// An object for building up a generated file line-by-line. -/// -/// After creation, make calls such as `writeLine` to build up the file, -/// and call `rendered` at the end to get the full file contents. -final class StringCodeWriter { - - /// The stored lines of code. - private var lines: [String] - - /// The current nesting level. - private var level: Int - - /// The indentation for each level as the number of spaces. - internal let indentation: Int - - /// Whether the next call to `writeLine` will continue writing to the last - /// stored line. Otherwise a new line is appended. - private var nextWriteAppendsToLastLine: Bool = false - - /// Creates a new empty writer. - init(indentation: Int) { - self.level = 0 - self.lines = [] - self.indentation = indentation - } - - /// Concatenates the stored lines of code into a single string. - /// - Returns: The contents of the full file in a single string. - func rendered() -> String { lines.joined(separator: "\n") } - - /// Writes a line of code. - /// - /// By default, a new line is appended to the file. - /// - /// To continue the last line, make a call to `nextLineAppendsToLastLine` - /// before calling `writeLine`. - /// - Parameter line: The contents of the line to write. - func writeLine(_ line: String) { - let newLine: String - if nextWriteAppendsToLastLine && !lines.isEmpty { - let existingLine = lines.removeLast() - newLine = existingLine + line - } else { - let indentation = Array(repeating: " ", count: self.indentation * level).joined() - newLine = indentation + line - } - lines.append(newLine) - nextWriteAppendsToLastLine = false - } - - /// Increases the indentation level by 1. - func push() { level += 1 } - - /// Decreases the indentation level by 1. - /// - Precondition: Current level must be greater than 0. - func pop() { - precondition(level > 0, "Cannot pop below 0") - level -= 1 - } - - /// Executes the provided closure with one level deeper indentation. - /// - Parameter work: The closure to execute. - /// - Returns: The result of the closure execution. - func withNestedLevel(_ work: () -> R) -> R { - push() - defer { pop() } - return work() - } - - /// Sets a flag on the writer so that the next call to `writeLine` continues - /// the last stored line instead of starting a new line. - /// - /// Safe to call repeatedly, it gets reset by `writeLine`. - func nextLineAppendsToLastLine() { nextWriteAppendsToLastLine = true } -} - -@available(*, unavailable) -extension TextBasedRenderer: Sendable {} - -/// A renderer that uses string interpolation and concatenation -/// to convert the provided structure code into raw string form. -struct TextBasedRenderer: RendererProtocol { - - func render( - structured: StructuredSwiftRepresentation - ) throws - -> SourceFile - { - let namedFile = structured.file - renderFile(namedFile.contents) - let string = writer.rendered() - return SourceFile(name: namedFile.name, contents: string) - } - - /// The underlying writer. - private let writer: StringCodeWriter - - /// Creates a new empty renderer. - static var `default`: TextBasedRenderer { .init(indentation: 4) } - - init(indentation: Int) { - self.writer = StringCodeWriter(indentation: indentation) - } - - // MARK: - Internals - - /// Returns the current contents of the writer as a string. - func renderedContents() -> String { writer.rendered() } - - /// Renders the specified Swift file. - func renderFile(_ description: FileDescription) { - if let topComment = description.topComment { - renderComment(topComment) - writer.writeLine("") - } - if let imports = description.imports { - renderImports(imports) - writer.writeLine("") - } - for (codeBlock, isLast) in description.codeBlocks.enumeratedWithLastMarker() { - renderCodeBlock(codeBlock) - if !isLast { - writer.writeLine("") - } - } - } - - /// Renders the specified comment. - func renderComment(_ comment: Comment) { - let prefix: String - let commentString: String - switch comment { - case .inline(let string): - prefix = "//" - commentString = string - case .doc(let string): - prefix = "///" - commentString = string - case .mark(let string, sectionBreak: true): - prefix = "// MARK: -" - commentString = string - case .mark(let string, sectionBreak: false): - prefix = "// MARK:" - commentString = string - case .preFormatted(let string): - prefix = "" - commentString = string - } - - let lines = commentString.transformingLines { line, isLast in - // The last line of a comment that is blank should be dropped. - // Pre formatted documentation might contain such lines. - if line.isEmpty && prefix.isEmpty && isLast { - return nil - } else { - let formattedPrefix = !prefix.isEmpty && !line.isEmpty ? "\(prefix) " : prefix - return "\(formattedPrefix)\(line)" - } - } - lines.forEach(writer.writeLine) - } - - /// Renders the specified import statements. - func renderImports(_ imports: [ImportDescription]?) { (imports ?? []).forEach(renderImport) } - - /// Renders a single import statement. - func renderImport(_ description: ImportDescription) { - func render(preconcurrency: Bool) { - let spiPrefix = description.spi.map { "@_spi(\($0)) " } ?? "" - let preconcurrencyPrefix = preconcurrency ? "@preconcurrency " : "" - let accessLevel = description.accessLevel.map { "\($0) " } ?? "" - - if let item = description.item { - writer.writeLine( - "\(preconcurrencyPrefix)\(spiPrefix)\(accessLevel)import \(item.kind) \(description.moduleName).\(item.name)" - ) - } else if let moduleTypes = description.moduleTypes { - for type in moduleTypes { - writer.writeLine("\(preconcurrencyPrefix)\(spiPrefix)\(accessLevel)import \(type)") - } - } else { - writer.writeLine( - "\(preconcurrencyPrefix)\(spiPrefix)\(accessLevel)import \(description.moduleName)" - ) - } - } - - switch description.preconcurrency { - case .always: render(preconcurrency: true) - case .never: render(preconcurrency: false) - case .onOS(let operatingSystems): - writer.writeLine("#if \(operatingSystems.map { "os(\($0))" }.joined(separator: " || "))") - render(preconcurrency: true) - writer.writeLine("#else") - render(preconcurrency: false) - writer.writeLine("#endif") - } - } - - /// Renders the specified access modifier. - func renderedAccessModifier(_ accessModifier: AccessModifier) -> String { - switch accessModifier { - case .public: return "public" - case .package: return "package" - case .internal: return "internal" - case .fileprivate: return "fileprivate" - case .private: return "private" - } - } - - /// Renders the specified identifier. - func renderIdentifier(_ identifier: IdentifierDescription) { - switch identifier { - case .pattern(let string): writer.writeLine(string) - case .type(let existingTypeDescription): - renderExistingTypeDescription(existingTypeDescription) - } - } - - /// Renders the specified member access expression. - func renderMemberAccess(_ memberAccess: MemberAccessDescription) { - if let left = memberAccess.left { - renderExpression(left) - writer.nextLineAppendsToLastLine() - } - writer.writeLine(".\(memberAccess.right)") - } - - /// Renders the specified function call argument. - func renderFunctionCallArgument(_ arg: FunctionArgumentDescription) { - if let left = arg.label { - writer.writeLine("\(left): ") - writer.nextLineAppendsToLastLine() - } - renderExpression(arg.expression) - } - - /// Renders the specified function call. - func renderFunctionCall(_ functionCall: FunctionCallDescription) { - renderExpression(functionCall.calledExpression) - writer.nextLineAppendsToLastLine() - writer.writeLine("(") - let arguments = functionCall.arguments - if arguments.count > 1 { - writer.withNestedLevel { - for (argument, isLast) in arguments.enumeratedWithLastMarker() { - renderFunctionCallArgument(argument) - if !isLast { - writer.nextLineAppendsToLastLine() - writer.writeLine(",") - } - } - } - } else { - writer.nextLineAppendsToLastLine() - if let argument = arguments.first { renderFunctionCallArgument(argument) } - writer.nextLineAppendsToLastLine() - } - writer.writeLine(")") - if let trailingClosure = functionCall.trailingClosure { - writer.nextLineAppendsToLastLine() - writer.writeLine(" ") - renderClosureInvocation(trailingClosure) - } - } - - /// Renders the specified assignment expression. - func renderAssignment(_ assignment: AssignmentDescription) { - renderExpression(assignment.left) - writer.nextLineAppendsToLastLine() - writer.writeLine(" = ") - writer.nextLineAppendsToLastLine() - renderExpression(assignment.right) - } - - /// Renders the specified switch case kind. - func renderSwitchCaseKind(_ kind: SwitchCaseKind) { - switch kind { - case let .`case`(expression, associatedValueNames): - let associatedValues: String - let maybeLet: String - if !associatedValueNames.isEmpty { - associatedValues = "(" + associatedValueNames.joined(separator: ", ") + ")" - maybeLet = "let " - } else { - associatedValues = "" - maybeLet = "" - } - writer.writeLine("case \(maybeLet)") - writer.nextLineAppendsToLastLine() - renderExpression(expression) - writer.nextLineAppendsToLastLine() - writer.writeLine(associatedValues) - case .multiCase(let expressions): - writer.writeLine("case ") - writer.nextLineAppendsToLastLine() - for (expression, isLast) in expressions.enumeratedWithLastMarker() { - renderExpression(expression) - writer.nextLineAppendsToLastLine() - if !isLast { writer.writeLine(", ") } - writer.nextLineAppendsToLastLine() - } - case .`default`: writer.writeLine("default") - } - } - - /// Renders the specified switch case. - func renderSwitchCase(_ switchCase: SwitchCaseDescription) { - renderSwitchCaseKind(switchCase.kind) - writer.nextLineAppendsToLastLine() - writer.writeLine(":") - writer.withNestedLevel { renderCodeBlocks(switchCase.body) } - } - - /// Renders the specified switch expression. - func renderSwitch(_ switchDesc: SwitchDescription) { - writer.writeLine("switch ") - writer.nextLineAppendsToLastLine() - renderExpression(switchDesc.switchedExpression) - writer.nextLineAppendsToLastLine() - writer.writeLine(" {") - for caseDesc in switchDesc.cases { renderSwitchCase(caseDesc) } - writer.writeLine("}") - } - - /// Renders the specified if statement. - func renderIf(_ ifDesc: IfStatementDescription) { - let ifBranch = ifDesc.ifBranch - writer.writeLine("if ") - writer.nextLineAppendsToLastLine() - renderExpression(ifBranch.condition) - writer.nextLineAppendsToLastLine() - writer.writeLine(" {") - writer.withNestedLevel { renderCodeBlocks(ifBranch.body) } - writer.writeLine("}") - for branch in ifDesc.elseIfBranches { - writer.nextLineAppendsToLastLine() - writer.writeLine(" else if ") - writer.nextLineAppendsToLastLine() - renderExpression(branch.condition) - writer.nextLineAppendsToLastLine() - writer.writeLine(" {") - writer.withNestedLevel { renderCodeBlocks(branch.body) } - writer.writeLine("}") - } - if let elseBody = ifDesc.elseBody { - writer.nextLineAppendsToLastLine() - writer.writeLine(" else {") - writer.withNestedLevel { renderCodeBlocks(elseBody) } - writer.writeLine("}") - } - } - - /// Renders the specified switch expression. - func renderDoStatement(_ description: DoStatementDescription) { - writer.writeLine("do {") - writer.withNestedLevel { renderCodeBlocks(description.doStatement) } - if let catchBody = description.catchBody { - writer.writeLine("} catch {") - if !catchBody.isEmpty { - writer.withNestedLevel { renderCodeBlocks(catchBody) } - } else { - writer.nextLineAppendsToLastLine() - } - } - writer.writeLine("}") - } - - /// Renders the specified value binding expression. - func renderValueBinding(_ valueBinding: ValueBindingDescription) { - writer.writeLine("\(renderedBindingKind(valueBinding.kind)) ") - writer.nextLineAppendsToLastLine() - renderFunctionCall(valueBinding.value) - } - - /// Renders the specified keyword. - func renderedKeywordKind(_ kind: KeywordKind) -> String { - switch kind { - case .return: return "return" - case .try(hasPostfixQuestionMark: let hasPostfixQuestionMark): - return "try\(hasPostfixQuestionMark ? "?" : "")" - case .await: return "await" - case .throw: return "throw" - case .yield: return "yield" - } - } - - /// Renders the specified unary keyword expression. - func renderUnaryKeywordExpression(_ expression: UnaryKeywordDescription) { - writer.writeLine(renderedKeywordKind(expression.kind)) - guard let expr = expression.expression else { return } - writer.nextLineAppendsToLastLine() - writer.writeLine(" ") - writer.nextLineAppendsToLastLine() - renderExpression(expr) - } - - /// Renders the specified closure invocation. - func renderClosureInvocation(_ invocation: ClosureInvocationDescription) { - writer.writeLine("{") - if !invocation.argumentNames.isEmpty { - writer.nextLineAppendsToLastLine() - writer.writeLine(" \(invocation.argumentNames.joined(separator: ", ")) in") - } - if let body = invocation.body { writer.withNestedLevel { renderCodeBlocks(body) } } - writer.writeLine("}") - } - - /// Renders the specified binary operator. - func renderedBinaryOperator(_ op: BinaryOperator) -> String { op.rawValue } - - /// Renders the specified binary operation. - func renderBinaryOperation(_ operation: BinaryOperationDescription) { - renderExpression(operation.left) - writer.nextLineAppendsToLastLine() - writer.writeLine(" \(renderedBinaryOperator(operation.operation)) ") - writer.nextLineAppendsToLastLine() - renderExpression(operation.right) - } - - /// Renders the specified inout expression. - func renderInOutDescription(_ description: InOutDescription) { - writer.writeLine("&") - writer.nextLineAppendsToLastLine() - renderExpression(description.referencedExpr) - } - - /// Renders the specified optional chaining expression. - func renderOptionalChainingDescription(_ description: OptionalChainingDescription) { - renderExpression(description.referencedExpr) - writer.nextLineAppendsToLastLine() - writer.writeLine("?") - } - - /// Renders the specified tuple expression. - func renderTupleDescription(_ description: TupleDescription) { - writer.writeLine("(") - writer.nextLineAppendsToLastLine() - let members = description.members - for (member, isLast) in members.enumeratedWithLastMarker() { - renderExpression(member) - if !isLast { - writer.nextLineAppendsToLastLine() - writer.writeLine(", ") - } - writer.nextLineAppendsToLastLine() - } - writer.writeLine(")") - } - - /// Renders the specified expression. - func renderExpression(_ expression: Expression) { - switch expression { - case .literal(let literalDescription): renderLiteral(literalDescription) - case .identifier(let identifierDescription): - renderIdentifier(identifierDescription) - case .memberAccess(let memberAccessDescription): renderMemberAccess(memberAccessDescription) - case .functionCall(let functionCallDescription): renderFunctionCall(functionCallDescription) - case .assignment(let assignment): renderAssignment(assignment) - case .switch(let switchDesc): renderSwitch(switchDesc) - case .ifStatement(let ifDesc): renderIf(ifDesc) - case .doStatement(let doStmt): renderDoStatement(doStmt) - case .valueBinding(let valueBinding): renderValueBinding(valueBinding) - case .unaryKeyword(let unaryKeyword): renderUnaryKeywordExpression(unaryKeyword) - case .closureInvocation(let closureInvocation): renderClosureInvocation(closureInvocation) - case .binaryOperation(let binaryOperation): renderBinaryOperation(binaryOperation) - case .inOut(let inOut): renderInOutDescription(inOut) - case .optionalChaining(let optionalChaining): - renderOptionalChainingDescription(optionalChaining) - case .tuple(let tuple): renderTupleDescription(tuple) - } - } - - /// Renders the specified literal expression. - func renderLiteral(_ literal: LiteralDescription) { - func write(_ string: String) { writer.writeLine(string) } - switch literal { - case let .string(string): - // Use a raw literal if the string contains a quote/backslash. - if string.contains("\"") || string.contains("\\") { - write("#\"\(string)\"#") - } else { - write("\"\(string)\"") - } - case let .int(int): write("\(int)") - case let .bool(bool): write(bool ? "true" : "false") - case .nil: write("nil") - case .array(let items): - writer.writeLine("[") - if !items.isEmpty { - writer.withNestedLevel { - for (item, isLast) in items.enumeratedWithLastMarker() { - renderExpression(item) - if !isLast { - writer.nextLineAppendsToLastLine() - writer.writeLine(",") - } - } - } - } else { - writer.nextLineAppendsToLastLine() - } - writer.writeLine("]") - - case .dictionary(let items): - writer.writeLine("[") - if items.isEmpty { - writer.nextLineAppendsToLastLine() - writer.writeLine(":") - writer.nextLineAppendsToLastLine() - } else { - writer.withNestedLevel { - for (item, isLast) in items.enumeratedWithLastMarker() { - renderExpression(item.key) - writer.nextLineAppendsToLastLine() - writer.writeLine(": ") - writer.nextLineAppendsToLastLine() - renderExpression(item.value) - if !isLast { - writer.nextLineAppendsToLastLine() - writer.writeLine(",") - } - } - } - } - writer.writeLine("]") - } - } - - /// Renders the specified where clause requirement. - func renderedWhereClauseRequirement(_ requirement: WhereClauseRequirement) -> String { - switch requirement { - case .conformance(let left, let right): return "\(left): \(right)" - } - } - - /// Renders the specified where clause. - func renderedWhereClause(_ clause: WhereClause) -> String { - let renderedRequirements = clause.requirements.map(renderedWhereClauseRequirement) - return "where \(renderedRequirements.joined(separator: ", "))" - } - - /// Renders the specified extension declaration. - func renderExtension(_ extensionDescription: ExtensionDescription) { - if let accessModifier = extensionDescription.accessModifier { - writer.writeLine(renderedAccessModifier(accessModifier) + " ") - writer.nextLineAppendsToLastLine() - } - writer.writeLine("extension \(extensionDescription.onType)") - writer.nextLineAppendsToLastLine() - if !extensionDescription.conformances.isEmpty { - writer.writeLine(": \(extensionDescription.conformances.joined(separator: ", "))") - writer.nextLineAppendsToLastLine() - } - if let whereClause = extensionDescription.whereClause { - writer.writeLine(" " + renderedWhereClause(whereClause)) - writer.nextLineAppendsToLastLine() - } - writer.writeLine(" {") - for (declaration, isLast) in extensionDescription.declarations.enumeratedWithLastMarker() { - writer.withNestedLevel { - renderDeclaration(declaration) - if !isLast { - writer.writeLine("") - } - } - } - writer.writeLine("}") - } - - /// Renders the specified type reference to an existing type. - func renderExistingTypeDescription(_ type: ExistingTypeDescription) { - switch type { - case .any(let existingTypeDescription): - writer.writeLine("any ") - writer.nextLineAppendsToLastLine() - renderExistingTypeDescription(existingTypeDescription) - case .generic(let wrapper, let wrapped): - renderExistingTypeDescription(wrapper) - writer.nextLineAppendsToLastLine() - writer.writeLine("<") - writer.nextLineAppendsToLastLine() - renderExistingTypeDescription(wrapped) - writer.nextLineAppendsToLastLine() - writer.writeLine(">") - case .optional(let existingTypeDescription): - renderExistingTypeDescription(existingTypeDescription) - writer.nextLineAppendsToLastLine() - writer.writeLine("?") - case .member(let components): - writer.writeLine(components.joined(separator: ".")) - case .array(let existingTypeDescription): - writer.writeLine("[") - writer.nextLineAppendsToLastLine() - renderExistingTypeDescription(existingTypeDescription) - writer.nextLineAppendsToLastLine() - writer.writeLine("]") - case .dictionaryValue(let existingTypeDescription): - writer.writeLine("[String: ") - writer.nextLineAppendsToLastLine() - renderExistingTypeDescription(existingTypeDescription) - writer.nextLineAppendsToLastLine() - writer.writeLine("]") - case .some(let existingTypeDescription): - writer.writeLine("some ") - writer.nextLineAppendsToLastLine() - renderExistingTypeDescription(existingTypeDescription) - case .closure(let closureSignatureDescription): - renderClosureSignature(closureSignatureDescription) - } - } - - /// Renders the specified typealias declaration. - func renderTypealias(_ alias: TypealiasDescription) { - var words: [String] = [] - if let accessModifier = alias.accessModifier { - words.append(renderedAccessModifier(accessModifier)) - } - words.append(contentsOf: [ - "typealias", alias.name, "=", - ]) - writer.writeLine(words.joinedWords() + " ") - writer.nextLineAppendsToLastLine() - renderExistingTypeDescription(alias.existingType) - } - - /// Renders the specified binding kind. - func renderedBindingKind(_ kind: BindingKind) -> String { - switch kind { - case .var: return "var" - case .let: return "let" - } - } - - /// Renders the specified variable declaration. - func renderVariable(_ variable: VariableDescription) { - do { - if let accessModifier = variable.accessModifier { - writer.writeLine(renderedAccessModifier(accessModifier) + " ") - writer.nextLineAppendsToLastLine() - } - if variable.isStatic { - writer.writeLine("static ") - writer.nextLineAppendsToLastLine() - } - writer.writeLine(renderedBindingKind(variable.kind) + " ") - writer.nextLineAppendsToLastLine() - renderExpression(variable.left) - if let type = variable.type { - writer.nextLineAppendsToLastLine() - writer.writeLine(": ") - writer.nextLineAppendsToLastLine() - renderExistingTypeDescription(type) - } - } - - if let right = variable.right { - writer.nextLineAppendsToLastLine() - writer.writeLine(" = ") - writer.nextLineAppendsToLastLine() - renderExpression(right) - } - - if let body = variable.getter { - writer.nextLineAppendsToLastLine() - writer.writeLine(" {") - writer.withNestedLevel { - let hasExplicitGetter = - !variable.getterEffects.isEmpty || variable.setter != nil || variable.modify != nil - if hasExplicitGetter { - let keywords = variable.getterEffects.map(renderedFunctionKeyword).joined(separator: " ") - let line = "get \(keywords) {" - writer.writeLine(line) - writer.push() - } - renderCodeBlocks(body) - if hasExplicitGetter { - writer.pop() - writer.writeLine("}") - } - if let modify = variable.modify { - writer.writeLine("_modify {") - writer.withNestedLevel { renderCodeBlocks(modify) } - writer.writeLine("}") - } - if let setter = variable.setter { - writer.writeLine("set {") - writer.withNestedLevel { renderCodeBlocks(setter) } - writer.writeLine("}") - } - } - writer.writeLine("}") - } - } - - /// Renders the specified struct declaration. - func renderStruct(_ structDesc: StructDescription) { - if let accessModifier = structDesc.accessModifier { - writer.writeLine(renderedAccessModifier(accessModifier) + " ") - writer.nextLineAppendsToLastLine() - } - writer.writeLine("struct \(structDesc.name)") - writer.nextLineAppendsToLastLine() - if !structDesc.conformances.isEmpty { - writer.writeLine(": \(structDesc.conformances.joined(separator: ", "))") - writer.nextLineAppendsToLastLine() - } - writer.writeLine(" {") - if !structDesc.members.isEmpty { - writer.withNestedLevel { - for (member, isLast) in structDesc.members.enumeratedWithLastMarker() { - renderDeclaration(member) - if !isLast { - writer.writeLine("") - } - } - } - } else { - writer.nextLineAppendsToLastLine() - } - writer.writeLine("}") - } - - /// Renders the specified protocol declaration. - func renderProtocol(_ protocolDesc: ProtocolDescription) { - if let accessModifier = protocolDesc.accessModifier { - writer.writeLine("\(renderedAccessModifier(accessModifier)) ") - writer.nextLineAppendsToLastLine() - } - writer.writeLine("protocol \(protocolDesc.name)") - writer.nextLineAppendsToLastLine() - if !protocolDesc.conformances.isEmpty { - let conformances = protocolDesc.conformances.joined(separator: ", ") - writer.writeLine(": \(conformances)") - writer.nextLineAppendsToLastLine() - } - writer.writeLine(" {") - if !protocolDesc.members.isEmpty { - writer.withNestedLevel { - for (member, isLast) in protocolDesc.members.enumeratedWithLastMarker() { - renderDeclaration(member) - if !isLast { - writer.writeLine("") - } - } - } - } else { - writer.nextLineAppendsToLastLine() - } - writer.writeLine("}") - } - - /// Renders the specified enum declaration. - func renderEnum(_ enumDesc: EnumDescription) { - if enumDesc.isFrozen { - writer.writeLine("@frozen ") - writer.nextLineAppendsToLastLine() - } - if let accessModifier = enumDesc.accessModifier { - writer.writeLine("\(renderedAccessModifier(accessModifier)) ") - writer.nextLineAppendsToLastLine() - } - if enumDesc.isIndirect { - writer.writeLine("indirect ") - writer.nextLineAppendsToLastLine() - } - writer.writeLine("enum \(enumDesc.name)") - writer.nextLineAppendsToLastLine() - if !enumDesc.conformances.isEmpty { - writer.writeLine(": \(enumDesc.conformances.joined(separator: ", "))") - writer.nextLineAppendsToLastLine() - } - writer.writeLine(" {") - if !enumDesc.members.isEmpty { - writer.withNestedLevel { for member in enumDesc.members { renderDeclaration(member) } } - } else { - writer.nextLineAppendsToLastLine() - } - writer.writeLine("}") - } - - /// Renders the specified enum case associated value. - func renderEnumCaseAssociatedValue(_ value: EnumCaseAssociatedValueDescription) { - var words: [String] = [] - if let label = value.label { words.append(label + ":") } - writer.writeLine(words.joinedWords()) - writer.nextLineAppendsToLastLine() - renderExistingTypeDescription(value.type) - } - - /// Renders the specified enum case declaration. - func renderEnumCase(_ enumCase: EnumCaseDescription) { - writer.writeLine("case \(enumCase.name)") - switch enumCase.kind { - case .nameOnly: break - case .nameWithRawValue(let rawValue): - writer.nextLineAppendsToLastLine() - writer.writeLine(" = ") - writer.nextLineAppendsToLastLine() - renderLiteral(rawValue) - case .nameWithAssociatedValues(let values): - if values.isEmpty { break } - for (value, isLast) in values.enumeratedWithLastMarker() { - renderEnumCaseAssociatedValue(value) - if !isLast { - writer.nextLineAppendsToLastLine() - writer.writeLine(", ") - } - } - } - } - - /// Renders the specified declaration. - func renderDeclaration(_ declaration: Declaration) { - switch declaration { - case let .commentable(comment, nestedDeclaration): - renderCommentableDeclaration(comment: comment, declaration: nestedDeclaration) - case let .deprecated(deprecation, nestedDeclaration): - renderDeprecatedDeclaration(deprecation: deprecation, declaration: nestedDeclaration) - case let .guarded(availability, nestedDeclaration): - renderGuardedDeclaration(availability: availability, declaration: nestedDeclaration) - case .variable(let variableDescription): renderVariable(variableDescription) - case .extension(let extensionDescription): renderExtension(extensionDescription) - case .struct(let structDescription): renderStruct(structDescription) - case .protocol(let protocolDescription): renderProtocol(protocolDescription) - case .enum(let enumDescription): renderEnum(enumDescription) - case .typealias(let typealiasDescription): renderTypealias(typealiasDescription) - case .function(let functionDescription): renderFunction(functionDescription) - case .enumCase(let enumCase): renderEnumCase(enumCase) - } - } - - /// Renders the specified function kind. - func renderedFunctionKind(_ functionKind: FunctionKind) -> String { - switch functionKind { - case .initializer(let isFailable): return "init\(isFailable ? "?" : "")" - case .function(let name, let isStatic): - return (isStatic ? "static " : "") + "func \(name)" - - } - } - - /// Renders the specified function keyword. - func renderedFunctionKeyword(_ keyword: FunctionKeyword) -> String { - switch keyword { - case .throws: return "throws" - case .async: return "async" - case .rethrows: return "rethrows" - } - } - - /// Renders the specified function signature. - func renderClosureSignature(_ signature: ClosureSignatureDescription) { - if signature.sendable { - writer.writeLine("@Sendable ") - writer.nextLineAppendsToLastLine() - } - if signature.escaping { - writer.writeLine("@escaping ") - writer.nextLineAppendsToLastLine() - } - - writer.writeLine("(") - let parameters = signature.parameters - let separateLines = parameters.count > 1 - if separateLines { - writer.withNestedLevel { - for (parameter, isLast) in signature.parameters.enumeratedWithLastMarker() { - renderClosureParameter(parameter) - if !isLast { - writer.nextLineAppendsToLastLine() - writer.writeLine(",") - } - } - } - } else { - writer.nextLineAppendsToLastLine() - if let parameter = parameters.first { - renderClosureParameter(parameter) - writer.nextLineAppendsToLastLine() - } - } - writer.writeLine(")") - - let keywords = signature.keywords - for keyword in keywords { - writer.nextLineAppendsToLastLine() - writer.writeLine(" " + renderedFunctionKeyword(keyword)) - } - - if let returnType = signature.returnType { - writer.nextLineAppendsToLastLine() - writer.writeLine(" -> ") - writer.nextLineAppendsToLastLine() - renderExpression(returnType) - } - } - - /// Renders the specified function signature. - func renderFunctionSignature(_ signature: FunctionSignatureDescription) { - do { - if let accessModifier = signature.accessModifier { - writer.writeLine(renderedAccessModifier(accessModifier) + " ") - writer.nextLineAppendsToLastLine() - } - let generics = signature.generics - writer.writeLine( - renderedFunctionKind(signature.kind) - ) - if !generics.isEmpty { - writer.nextLineAppendsToLastLine() - writer.writeLine("<") - for (genericType, isLast) in generics.enumeratedWithLastMarker() { - writer.nextLineAppendsToLastLine() - renderExistingTypeDescription(genericType) - if !isLast { - writer.nextLineAppendsToLastLine() - writer.writeLine(", ") - } - } - writer.nextLineAppendsToLastLine() - writer.writeLine(">") - } - writer.nextLineAppendsToLastLine() - writer.writeLine("(") - let parameters = signature.parameters - let separateLines = parameters.count > 1 - if separateLines { - writer.withNestedLevel { - for (parameter, isLast) in signature.parameters.enumeratedWithLastMarker() { - renderParameter(parameter) - if !isLast { - writer.nextLineAppendsToLastLine() - writer.writeLine(",") - } - } - } - } else { - writer.nextLineAppendsToLastLine() - if let parameter = parameters.first { renderParameter(parameter) } - writer.nextLineAppendsToLastLine() - } - writer.writeLine(")") - } - - do { - let keywords = signature.keywords - if !keywords.isEmpty { - for keyword in keywords { - writer.nextLineAppendsToLastLine() - writer.writeLine(" " + renderedFunctionKeyword(keyword)) - } - } - } - - if let returnType = signature.returnType { - writer.nextLineAppendsToLastLine() - writer.writeLine(" -> ") - writer.nextLineAppendsToLastLine() - renderExpression(returnType) - } - - if let whereClause = signature.whereClause { - writer.nextLineAppendsToLastLine() - writer.writeLine(" " + renderedWhereClause(whereClause)) - } - } - - /// Renders the specified function declaration. - func renderFunction(_ functionDescription: FunctionDescription) { - renderFunctionSignature(functionDescription.signature) - guard let body = functionDescription.body else { return } - writer.nextLineAppendsToLastLine() - writer.writeLine(" {") - if !body.isEmpty { - writer.withNestedLevel { renderCodeBlocks(body) } - } else { - writer.nextLineAppendsToLastLine() - } - writer.writeLine("}") - } - - /// Renders the specified parameter declaration. - func renderParameter(_ parameterDescription: ParameterDescription) { - if let label = parameterDescription.label { - writer.writeLine(label) - } else { - writer.writeLine("_") - } - writer.nextLineAppendsToLastLine() - if let name = parameterDescription.name, name != parameterDescription.label { - // If the label and name are the same value, don't repeat it. - writer.writeLine(" ") - writer.nextLineAppendsToLastLine() - writer.writeLine(name) - writer.nextLineAppendsToLastLine() - } - writer.writeLine(": ") - writer.nextLineAppendsToLastLine() - - if parameterDescription.inout { - writer.writeLine("inout ") - writer.nextLineAppendsToLastLine() - } - - if let type = parameterDescription.type { - renderExistingTypeDescription(type) - } - - if let defaultValue = parameterDescription.defaultValue { - writer.nextLineAppendsToLastLine() - writer.writeLine(" = ") - writer.nextLineAppendsToLastLine() - renderExpression(defaultValue) - } - } - - /// Renders the specified parameter declaration for a closure. - func renderClosureParameter(_ parameterDescription: ParameterDescription) { - let name = parameterDescription.name - let label: String - if let declaredLabel = parameterDescription.label { - label = declaredLabel - } else { - label = "_" - } - - if let name = name { - writer.writeLine(label) - if name != parameterDescription.label { - // If the label and name are the same value, don't repeat it. - writer.writeLine(" ") - writer.nextLineAppendsToLastLine() - writer.writeLine(name) - writer.nextLineAppendsToLastLine() - } - } - - if parameterDescription.inout { - writer.writeLine("inout ") - writer.nextLineAppendsToLastLine() - } - - if let type = parameterDescription.type { - renderExistingTypeDescription(type) - } - - if let defaultValue = parameterDescription.defaultValue { - writer.nextLineAppendsToLastLine() - writer.writeLine(" = ") - writer.nextLineAppendsToLastLine() - renderExpression(defaultValue) - } - } - - /// Renders the specified declaration with a comment. - func renderCommentableDeclaration(comment: Comment?, declaration: Declaration) { - if let comment { renderComment(comment) } - renderDeclaration(declaration) - } - - /// Renders the specified declaration with a deprecation annotation. - func renderDeprecatedDeclaration(deprecation: DeprecationDescription, declaration: Declaration) { - renderDeprecation(deprecation) - renderDeclaration(declaration) - } - - func renderDeprecation(_ deprecation: DeprecationDescription) { - let things: [String] = [ - "*", "deprecated", deprecation.message.map { "message: \"\($0)\"" }, - deprecation.renamed.map { "renamed: \"\($0)\"" }, - ] - .compactMap({ $0 }) - let line = "@available(\(things.joined(separator: ", ")))" - writer.writeLine(line) - } - - /// Renders the specified declaration with an availability guard annotation. - func renderGuardedDeclaration(availability: AvailabilityDescription, declaration: Declaration) { - renderAvailability(availability) - renderDeclaration(declaration) - } - - func renderAvailability(_ availability: AvailabilityDescription) { - var line = "@available(" - for osVersion in availability.osVersions { - line.append("\(osVersion.os.name) \(osVersion.version), ") - } - line.append("*)") - writer.writeLine(line) - } - - /// Renders the specified code block item. - func renderCodeBlockItem(_ description: CodeBlockItem) { - switch description { - case .declaration(let declaration): renderDeclaration(declaration) - case .expression(let expression): renderExpression(expression) - } - } - - /// Renders the specified code block. - func renderCodeBlock(_ description: CodeBlock) { - if let comment = description.comment { renderComment(comment) } - let item = description.item - renderCodeBlockItem(item) - } - - /// Renders the specified code blocks. - func renderCodeBlocks(_ blocks: [CodeBlock]) { blocks.forEach(renderCodeBlock) } -} - -extension Array { - - /// Returns a collection of tuples, where the first element is - /// the collection element and the second is a Boolean value indicating - /// whether it is the last element in the collection. - /// - Returns: A collection of tuples. - fileprivate func enumeratedWithLastMarker() -> [(Element, isLast: Bool)] { - let count = count - return enumerated().map { index, element in (element, index == count - 1) } - } -} - -extension Array where Element == String { - /// Returns a string where the elements of the array are joined - /// by a space character. - /// - Returns: A string with the elements of the array joined by space characters. - fileprivate func joinedWords() -> String { joined(separator: " ") } -} - -extension String { - - /// Returns an array of strings, where each string represents one line - /// in the current string. - /// - Returns: An array of strings, each representing one line in the original string. - fileprivate func asLines() -> [String] { - split(omittingEmptySubsequences: false, whereSeparator: \.isNewline).map(String.init) - } - - /// Returns a new string where the provided closure transforms each line. - /// The closure takes a string representing one line as a parameter. - /// - Parameter work: The closure that transforms each line. - /// - Returns: A new string where each line has been transformed using the given closure. - fileprivate func transformingLines(_ work: (String, Bool) -> String?) -> [String] { - asLines().enumeratedWithLastMarker().compactMap(work) - } -} - -extension TextBasedRenderer { - - /// Returns the provided expression rendered as a string. - /// - Parameter expression: The expression. - /// - Returns: The string representation of the expression. - static func renderedExpressionAsString(_ expression: Expression) -> String { - let renderer = TextBasedRenderer.default - renderer.renderExpression(expression) - return renderer.renderedContents() - } -} diff --git a/Sources/GRPCCodeGen/Internal/StructuredSwiftRepresentation.swift b/Sources/GRPCCodeGen/Internal/StructuredSwiftRepresentation.swift deleted file mode 100644 index c6171f72f..000000000 --- a/Sources/GRPCCodeGen/Internal/StructuredSwiftRepresentation.swift +++ /dev/null @@ -1,1918 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ -//===----------------------------------------------------------------------===// -// -// This source file is part of the SwiftOpenAPIGenerator open source project -// -// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -/// A description of an import declaration. -/// -/// For example: `import Foo`. -struct ImportDescription: Equatable, Codable { - /// The access level of the imported module. - /// - /// For example, the `public` in `public import Foo`. - /// - /// - Note: This is optional, as explicit access-level modifiers are not required on `import` statements. - var accessLevel: AccessModifier? = nil - - /// The name of the imported module. - /// - /// For example, the `Foo` in `import Foo`. - var moduleName: String - - /// An array of module types imported from the module, if applicable. - /// - /// For example, if there are type imports like `import Foo.Bar`, they would be listed here. - var moduleTypes: [String]? - - /// The name of the private interface for an `@_spi` import. - /// - /// For example, if `spi` was "Secret" and the module name was "Foo" then the import - /// would be `@_spi(Secret) import Foo`. - var spi: String? = nil - - /// Requirements for the `@preconcurrency` attribute. - var preconcurrency: PreconcurrencyRequirement = .never - - /// If the dependency is an item, the property's value is the item representation. - /// If the dependency is a module, this property is nil. - var item: Item? = nil - - /// Describes any requirement for the `@preconcurrency` attribute. - enum PreconcurrencyRequirement: Equatable, Codable { - /// The attribute is always required. - case always - /// The attribute is not required. - case never - /// The attribute is required only on the named operating systems. - case onOS([String]) - } - - /// Represents an item imported from a module. - struct Item: Equatable, Codable { - /// The keyword that specifies the item's kind (e.g. `func`, `struct`). - var kind: Kind - - /// The name of the imported item. - var name: String - - init(kind: Kind, name: String) { - self.kind = kind - self.name = name - } - } - - enum Kind: String, Equatable, Codable { - case `typealias` - case `struct` - case `class` - case `enum` - case `protocol` - case `let` - case `var` - case `func` - } -} - -/// A description of an access modifier. -/// -/// For example: `public`. -internal enum AccessModifier: String, Sendable, Equatable, Codable { - /// A declaration accessible outside of the module. - case `public` - - /// A declaration accessible outside of the module but only inside the containing package or project. - case `package` - - /// A declaration only accessible inside of the module. - case `internal` - - /// A declaration only accessible inside the same Swift file. - case `fileprivate` - - /// A declaration only accessible inside the same type or scope. - case `private` -} - -/// A description of a comment. -/// -/// For example `/// Hello`. -enum Comment: Equatable, Codable { - - /// An inline comment. - /// - /// For example: `// Great code below`. - case inline(String) - - /// A documentation comment. - /// - /// For example: `/// Important type`. - case doc(String) - - /// A mark comment. - /// - /// For example: `// MARK: - Public methods`, with the optional - /// section break (`-`). - case mark(String, sectionBreak: Bool) - - /// A comment that is already formatted, - /// meaning that it already has the `///` and - /// can contain multiple lines - /// - /// For example both the string and the comment - /// can look like `/// Important type`. - case preFormatted(String) -} - -/// A description of a literal. -/// -/// For example `"hello"` or `42`. -enum LiteralDescription: Equatable, Codable { - - /// A string literal. - /// - /// For example `"hello"`. - case string(String) - - /// An integer literal. - /// - /// For example `42`. - case int(Int) - - /// A Boolean literal. - /// - /// For example `true`. - case bool(Bool) - - /// The nil literal: `nil`. - case `nil` - - /// An array literal. - /// - /// For example `["hello", 42]`. - case array([Expression]) - - /// A dictionary literal. - /// - /// For example: `["hello": "42"]` - case dictionary([KeyValue]) - - struct KeyValue: Codable, Equatable { - var key: Expression - var value: Expression - } -} - -/// A description of an identifier, such as a variable name. -/// -/// For example, in `let foo = 42`, `foo` is an identifier. -enum IdentifierDescription: Equatable, Codable { - - /// A pattern identifier. - /// - /// For example, `foo` in `let foo = 42`. - case pattern(String) - - /// A type identifier. - /// - /// For example, `Swift.String` in `let foo: Swift.String = "hi"`. - case type(ExistingTypeDescription) -} - -/// A description of a member access expression. -/// -/// For example `foo.bar`. -struct MemberAccessDescription: Equatable, Codable { - - /// The expression of which a member `right` is accessed. - /// - /// For example, in `foo.bar`, `left` represents `foo`. - var left: Expression? - - /// The member name to access. - /// - /// For example, in `foo.bar`, `right` is `bar`. - var right: String -} - -/// A description of a function argument. -/// -/// For example in `foo(bar: 42)`, the function argument is `bar: 42`. -struct FunctionArgumentDescription: Equatable, Codable { - - /// An optional label of the function argument. - /// - /// For example, in `foo(bar: 42)`, the `label` is `bar`. - var label: String? - - /// The expression passed as the function argument value. - /// - /// For example, in `foo(bar: 42)`, `expression` represents `42`. - var expression: Expression -} - -/// A description of a function call. -/// -/// For example `foo(bar: 42)`. -struct FunctionCallDescription: Equatable, Codable { - - /// The expression that returns the function to be called. - /// - /// For example, in `foo(bar: 42)`, `calledExpression` represents `foo`. - var calledExpression: Expression - - /// The arguments to be passed to the function. - var arguments: [FunctionArgumentDescription] - - /// A trailing closure. - var trailingClosure: ClosureInvocationDescription? - - /// Creates a new function call description. - /// - Parameters: - /// - calledExpression: An expression that returns the function to be called. - /// - arguments: Arguments to be passed to the function. - /// - trailingClosure: A trailing closure. - init( - calledExpression: Expression, - arguments: [FunctionArgumentDescription] = [], - trailingClosure: ClosureInvocationDescription? = nil - ) { - self.calledExpression = calledExpression - self.arguments = arguments - self.trailingClosure = trailingClosure - } - - /// Creates a new function call description. - /// - Parameters: - /// - calledExpression: An expression that returns the function to be called. - /// - arguments: Arguments to be passed to the function. - /// - trailingClosure: A trailing closure. - init( - calledExpression: Expression, - arguments: [Expression], - trailingClosure: ClosureInvocationDescription? = nil - ) { - self.init( - calledExpression: calledExpression, - arguments: arguments.map { .init(label: nil, expression: $0) }, - trailingClosure: trailingClosure - ) - } -} - -/// A type of a variable binding: `let` or `var`. -enum BindingKind: Equatable, Codable { - - /// A mutable variable. - case `var` - - /// An immutable variable. - case `let` -} - -/// A description of a variable declaration. -/// -/// For example `let foo = 42`. -struct VariableDescription: Equatable, Codable { - - /// An access modifier. - var accessModifier: AccessModifier? - - /// A Boolean value that indicates whether the variable is static. - var isStatic: Bool = false - - /// The variable binding kind. - var kind: BindingKind - - /// The name of the variable. - /// - /// For example, in `let foo = 42`, `left` is `foo`. - var left: Expression - - /// The type of the variable. - /// - /// For example, in `let foo: Int = 42`, `type` is `Int`. - var type: ExistingTypeDescription? - - /// The expression to be assigned to the variable. - /// - /// For example, in `let foo = 42`, `right` represents `42`. - var right: Expression? = nil - - /// Body code for the getter. - /// - /// For example, in `var foo: Int { 42 }`, `body` represents `{ 42 }`. - var getter: [CodeBlock]? = nil - - /// Effects for the getter. - /// - /// For example, in `var foo: Int { get throws { 42 } }`, effects are `[.throws]`. - var getterEffects: [FunctionKeyword] = [] - - /// Body code for the setter. - /// - /// For example, in `var foo: Int { set { _foo = newValue } }`, `body` - /// represents `{ _foo = newValue }`. - var setter: [CodeBlock]? = nil - - /// Body code for the `_modify` accessor. - /// - /// For example, in `var foo: Int { _modify { yield &_foo } }`, `body` - /// represents `{ yield &_foo }`. - var modify: [CodeBlock]? = nil -} - -/// A requirement of a where clause. -enum WhereClauseRequirement: Equatable, Codable { - - /// A conformance requirement. - /// - /// For example, in `extension Array where Element: Foo {`, the first tuple value is `Element` and the second `Foo`. - case conformance(String, String) -} - -/// A description of a where clause. -/// -/// For example: `extension Array where Element: Foo {`. -struct WhereClause: Equatable, Codable { - - /// One or more requirements to be added after the `where` keyword. - var requirements: [WhereClauseRequirement] -} - -/// A description of an extension declaration. -/// -/// For example `extension Foo {`. -struct ExtensionDescription: Equatable, Codable { - - /// An access modifier. - var accessModifier: AccessModifier? = nil - - /// The name of the extended type. - /// - /// For example, in `extension Foo {`, `onType` is `Foo`. - var onType: String - - /// Additional type names that the extension conforms to. - /// - /// For example: `["Sendable", "Codable"]`. - var conformances: [String] = [] - - /// A where clause constraining the extension declaration. - var whereClause: WhereClause? = nil - - /// The declarations that the extension adds on the extended type. - var declarations: [Declaration] -} - -/// A description of a struct declaration. -/// -/// For example `struct Foo {`. -struct StructDescription: Equatable, Codable { - - /// An access modifier. - var accessModifier: AccessModifier? = nil - - /// The name of the struct. - /// - /// For example, in `struct Foo {`, `name` is `Foo`. - var name: String - - /// The type names that the struct conforms to. - /// - /// For example: `["Sendable", "Codable"]`. - var conformances: [String] = [] - - /// The declarations that make up the main struct body. - var members: [Declaration] = [] -} - -/// A description of an enum declaration. -/// -/// For example `enum Bar {`. -struct EnumDescription: Equatable, Codable { - - /// A Boolean value that indicates whether the enum has a `@frozen` - /// attribute. - var isFrozen: Bool = false - - /// A Boolean value that indicates whether the enum has the `indirect` - /// keyword. - var isIndirect: Bool = false - - /// An access modifier. - var accessModifier: AccessModifier? = nil - - /// The name of the enum. - /// - /// For example, in `enum Bar {`, `name` is `Bar`. - var name: String - - /// The type names that the enum conforms to. - /// - /// For example: `["Sendable", "Codable"]`. - var conformances: [String] = [] - - /// The declarations that make up the enum body. - var members: [Declaration] = [] -} - -/// A description of a type reference. -indirect enum ExistingTypeDescription: Equatable, Codable { - - /// A type with the `any` keyword in front of it. - /// - /// For example, `any Foo`. - case any(ExistingTypeDescription) - - /// An optional type. - /// - /// For example, `Foo?`. - case optional(ExistingTypeDescription) - - /// A wrapper type generic over a wrapped type. - /// - /// For example, `Wrapper`. - case generic(wrapper: ExistingTypeDescription, wrapped: ExistingTypeDescription) - - /// A type reference represented by the components. - /// - /// For example, `MyModule.Foo`. - case member([String]) - - /// An array with an element type. - /// - /// For example, `[Foo]`. - case array(ExistingTypeDescription) - - /// A dictionary where the key is `Swift.String` and the value is - /// the provided type. - /// - /// For example, `[String: Foo]`. - case dictionaryValue(ExistingTypeDescription) - - /// A type with the `some` keyword in front of it. - /// - /// For example, `some Foo`. - case some(ExistingTypeDescription) - - /// A closure signature as a type. - /// - /// For example: `(String) async throws -> Int`. - case closure(ClosureSignatureDescription) -} - -/// A description of a typealias declaration. -/// -/// For example `typealias Foo = Int`. -struct TypealiasDescription: Equatable, Codable { - - /// An access modifier. - var accessModifier: AccessModifier? - - /// The name of the typealias. - /// - /// For example, in `typealias Foo = Int`, `name` is `Foo`. - var name: String - - /// The existing type that serves as the underlying type of the alias. - /// - /// For example, in `typealias Foo = Int`, `existingType` is `Int`. - var existingType: ExistingTypeDescription -} - -/// A description of a protocol declaration. -/// -/// For example `protocol Foo {`. -struct ProtocolDescription: Equatable, Codable { - - /// An access modifier. - var accessModifier: AccessModifier? = nil - - /// The name of the protocol. - /// - /// For example, in `protocol Foo {`, `name` is `Foo`. - var name: String - - /// The type names that the protocol conforms to. - /// - /// For example: `["Sendable", "Codable"]`. - var conformances: [String] = [] - - /// The function and property declarations that make up the protocol - /// requirements. - var members: [Declaration] = [] -} - -/// A description of a function parameter declaration. -/// -/// For example, in `func foo(bar baz: String = "hi")`, the parameter -/// description represents `bar baz: String = "hi"` -struct ParameterDescription: Equatable, Codable { - - /// An external parameter label. - /// - /// For example, in `bar baz: String = "hi"`, `label` is `bar`. - var label: String? = nil - - /// An internal parameter name. - /// - /// For example, in `bar baz: String = "hi"`, `name` is `baz`. - var name: String? = nil - - /// The type name of the parameter. - /// - /// For example, in `bar baz: String = "hi"`, `type` is `String`. - var type: ExistingTypeDescription? = nil - - /// A default value of the parameter. - /// - /// For example, in `bar baz: String = "hi"`, `defaultValue` - /// represents `"hi"`. - var defaultValue: Expression? = nil - - /// An inout parameter type. - /// - /// For example, `bar baz: inout String`. - var `inout`: Bool = false -} - -/// A function kind: `func` or `init`. -enum FunctionKind: Equatable, Codable { - - /// An initializer. - /// - /// For example: `init()`, or `init?()` when `failable` is `true`. - case initializer(failable: Bool) - - /// A function or a method. Can be static. - /// - /// For example `foo()`, where `name` is `foo`. - case function( - name: String, - isStatic: Bool - ) -} - -/// A function keyword, such as `async` and `throws`. -enum FunctionKeyword: Equatable, Codable { - - /// An asynchronous function. - case `async` - - /// A function that can throw an error. - case `throws` - - /// A function that can rethrow an error. - case `rethrows` -} - -/// A description of a function signature. -/// -/// For example: `func foo(bar: String) async throws -> Int`. -struct FunctionSignatureDescription: Equatable, Codable { - - /// An access modifier. - var accessModifier: AccessModifier? = nil - - /// The kind of the function. - var kind: FunctionKind - - /// The generic types of the function. - var generics: [ExistingTypeDescription] = [] - - /// The parameters of the function. - var parameters: [ParameterDescription] = [] - - /// The keywords of the function, such as `async` and `throws.` - var keywords: [FunctionKeyword] = [] - - /// The return type name of the function, such as `Int`. - var returnType: Expression? = nil - - /// The where clause for a generic function. - var whereClause: WhereClause? -} - -/// A description of a function definition. -/// -/// For example: `func foo() { }`. -struct FunctionDescription: Equatable, Codable { - - /// The signature of the function. - var signature: FunctionSignatureDescription - - /// The body definition of the function. - /// - /// If nil, does not generate `{` and `}` at all for the body scope. - var body: [CodeBlock]? = nil - - /// Creates a new function description. - /// - Parameters: - /// - signature: The signature of the function. - /// - body: The body definition of the function. - init(signature: FunctionSignatureDescription, body: [CodeBlock]? = nil) { - self.signature = signature - self.body = body - } - - /// Creates a new function description. - /// - Parameters: - /// - accessModifier: An access modifier. - /// - kind: The kind of the function. - /// - parameters: The parameters of the function. - /// - keywords: The keywords of the function, such as `async`. - /// - returnType: The return type name of the function, such as `Int`. - /// - body: The body definition of the function. - init( - accessModifier: AccessModifier? = nil, - kind: FunctionKind, - generics: [ExistingTypeDescription] = [], - parameters: [ParameterDescription] = [], - keywords: [FunctionKeyword] = [], - returnType: Expression? = nil, - whereClause: WhereClause? = nil, - body: [CodeBlock]? = nil - ) { - self.signature = .init( - accessModifier: accessModifier, - kind: kind, - generics: generics, - parameters: parameters, - keywords: keywords, - returnType: returnType, - whereClause: whereClause - ) - self.body = body - } - - /// Creates a new function description. - /// - Parameters: - /// - accessModifier: An access modifier. - /// - kind: The kind of the function. - /// - parameters: The parameters of the function. - /// - keywords: The keywords of the function, such as `async`. - /// - returnType: The return type name of the function, such as `Int`. - /// - body: The body definition of the function. - init( - accessModifier: AccessModifier? = nil, - kind: FunctionKind, - generics: [ExistingTypeDescription] = [], - parameters: [ParameterDescription] = [], - keywords: [FunctionKeyword] = [], - returnType: Expression? = nil, - whereClause: WhereClause? = nil, - body: [Expression] - ) { - self.init( - accessModifier: accessModifier, - kind: kind, - generics: generics, - parameters: parameters, - keywords: keywords, - returnType: returnType, - whereClause: whereClause, - body: body.map { .expression($0) } - ) - } -} - -/// A description of a closure signature. -/// -/// For example: `(String) async throws -> Int`. -struct ClosureSignatureDescription: Equatable, Codable { - /// The parameters of the function. - var parameters: [ParameterDescription] = [] - - /// The keywords of the function, such as `async` and `throws.` - var keywords: [FunctionKeyword] = [] - - /// The return type name of the function, such as `Int`. - var returnType: Expression? = nil - - /// The ``@Sendable`` attribute. - var sendable: Bool = false - - /// The ``@escaping`` attribute. - var escaping: Bool = false -} - -/// A description of the associated value of an enum case. -/// -/// For example, in `case foo(bar: String)`, the associated value -/// represents `bar: String`. -struct EnumCaseAssociatedValueDescription: Equatable, Codable { - - /// A variable label. - /// - /// For example, in `bar: String`, `label` is `bar`. - var label: String? - - /// A variable type name. - /// - /// For example, in `bar: String`, `type` is `String`. - var type: ExistingTypeDescription -} - -/// An enum case kind. -/// -/// For example: `case foo` versus `case foo(String)`, and so on. -enum EnumCaseKind: Equatable, Codable { - - /// A case with only a name. - /// - /// For example: `case foo`. - case nameOnly - - /// A case with a name and a raw value. - /// - /// For example: `case foo = "Foo"`. - case nameWithRawValue(LiteralDescription) - - /// A case with a name and associated values. - /// - /// For example: `case foo(String)`. - case nameWithAssociatedValues([EnumCaseAssociatedValueDescription]) -} - -/// A description of an enum case. -/// -/// For example: `case foo(String)`. -struct EnumCaseDescription: Equatable, Codable { - - /// The name of the enum case. - /// - /// For example, in `case foo`, `name` is `foo`. - var name: String - - /// The kind of the enum case. - var kind: EnumCaseKind = .nameOnly -} - -/// A declaration of a Swift entity. -indirect enum Declaration: Equatable, Codable { - - /// A declaration that adds a comment on top of the provided declaration. - case commentable(Comment?, Declaration) - - /// A declaration that adds a comment on top of the provided declaration. - case deprecated(DeprecationDescription, Declaration) - - /// A declaration that adds an availability guard on top of the provided declaration. - case guarded(AvailabilityDescription, Declaration) - - /// A variable declaration. - case variable(VariableDescription) - - /// An extension declaration. - case `extension`(ExtensionDescription) - - /// A struct declaration. - case `struct`(StructDescription) - - /// An enum declaration. - case `enum`(EnumDescription) - - /// A typealias declaration. - case `typealias`(TypealiasDescription) - - /// A protocol declaration. - case `protocol`(ProtocolDescription) - - /// A function declaration. - case function(FunctionDescription) - - /// An enum case declaration. - case enumCase(EnumCaseDescription) -} - -/// A description of a deprecation notice. -/// -/// For example: `@available(*, deprecated, message: "This is going away", renamed: "other(param:)")` -struct DeprecationDescription: Equatable, Codable { - - /// A message used by the deprecation attribute. - var message: String? - - /// A new name of the symbol, allowing the user to get a fix-it. - var renamed: String? -} - -/// A description of an availability guard. -/// -/// For example: `@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)` -struct AvailabilityDescription: Equatable, Codable { - /// The array of OSes and versions which are specified in the availability guard. - var osVersions: [OSVersion] - init(osVersions: [OSVersion]) { - self.osVersions = osVersions - } - - /// An OS and its version. - struct OSVersion: Equatable, Codable { - var os: OS - var version: String - init(os: OS, version: String) { - self.os = os - self.version = version - } - } - - /// One of the possible OSes. - // swift-format-ignore: DontRepeatTypeInStaticProperties - struct OS: Equatable, Codable { - var name: String - - init(name: String) { - self.name = name - } - - static let macOS = Self(name: "macOS") - static let iOS = Self(name: "iOS") - static let watchOS = Self(name: "watchOS") - static let tvOS = Self(name: "tvOS") - static let visionOS = Self(name: "visionOS") - } -} - -/// A description of an assignment expression. -/// -/// For example: `foo = 42`. -struct AssignmentDescription: Equatable, Codable { - - /// The left-hand side expression, the variable to assign to. - /// - /// For example, in `foo = 42`, `left` is `foo`. - var left: Expression - - /// The right-hand side expression, the value to assign. - /// - /// For example, in `foo = 42`, `right` is `42`. - var right: Expression -} - -/// A switch case kind, either a `case` or a `default`. -enum SwitchCaseKind: Equatable, Codable { - - /// A case. - /// - /// For example: `case let foo(bar):`. - case `case`(Expression, [String]) - - /// A case with multiple comma-separated expressions. - /// - /// For example: `case "foo", "bar":`. - case multiCase([Expression]) - - /// A default. Spelled as `default:`. - case `default` -} - -/// A description of a switch case definition. -/// -/// For example: `case foo: print("foo")`. -struct SwitchCaseDescription: Equatable, Codable { - - /// The kind of the switch case. - var kind: SwitchCaseKind - - /// The body of the switch case. - /// - /// For example, in `case foo: print("foo")`, `body` - /// represents `print("foo")`. - var body: [CodeBlock] -} - -/// A description of a switch statement expression. -/// -/// For example: `switch foo {`. -struct SwitchDescription: Equatable, Codable { - - /// The expression evaluated by the switch statement. - /// - /// For example, in `switch foo {`, `switchedExpression` is `foo`. - var switchedExpression: Expression - - /// The cases defined in the switch statement. - var cases: [SwitchCaseDescription] -} - -/// A description of an if branch and the corresponding code block. -/// -/// For example: in `if foo { bar }`, the condition pair represents -/// `foo` + `bar`. -struct IfBranch: Equatable, Codable { - - /// The expressions evaluated by the if statement and their corresponding - /// body blocks. If more than one is provided, an `else if` branch is added. - /// - /// For example, in `if foo { bar }`, `condition` is `foo`. - var condition: Expression - - /// The body executed if the `condition` evaluates to true. - /// - /// For example, in `if foo { bar }`, `body` is `bar`. - var body: [CodeBlock] -} - -/// A description of an if[[/elseif]/else] statement expression. -/// -/// For example: `if foo { } else if bar { } else { }`. -struct IfStatementDescription: Equatable, Codable { - - /// The primary `if` branch. - var ifBranch: IfBranch - - /// Additional `else if` branches. - var elseIfBranches: [IfBranch] - - /// The body of an else block. - /// - /// No `else` statement is added when `elseBody` is nil. - var elseBody: [CodeBlock]? -} - -/// A description of a do statement. -/// -/// For example: `do { try foo() } catch { return bar }`. -struct DoStatementDescription: Equatable, Codable { - - /// The code blocks in the `do` statement body. - /// - /// For example, in `do { try foo() } catch { return bar }`, - /// `doBody` is `try foo()`. - var doStatement: [CodeBlock] - - /// The code blocks in the `catch` statement. - /// - /// If nil, no `catch` statement is added. - /// - /// For example, in `do { try foo() } catch { return bar }`, - /// `catchBody` is `return bar`. - var catchBody: [CodeBlock]? -} - -/// A description of a value binding used in enums with associated values. -/// -/// For example: `let foo(bar)`. -struct ValueBindingDescription: Equatable, Codable { - - /// The binding kind: `let` or `var`. - var kind: BindingKind - - /// The bound values in a function call expression syntax. - /// - /// For example, in `let foo(bar)`, `value` represents `foo(bar)`. - var value: FunctionCallDescription -} - -/// A kind of a keyword. -enum KeywordKind: Equatable, Codable { - - /// The return keyword. - case `return` - - /// The try keyword. - case `try`(hasPostfixQuestionMark: Bool) - - /// The await keyword. - case `await` - - /// The throw keyword. - case `throw` - - /// The yield keyword. - case `yield` -} - -/// A description of an expression that places a keyword before an expression. -struct UnaryKeywordDescription: Equatable, Codable { - - /// The keyword to place before the expression. - /// - /// For example, in `return foo`, `kind` represents `return`. - var kind: KeywordKind - - /// The expression prefixed by the keyword. - /// - /// For example, in `return foo`, `expression` represents `foo`. - var expression: Expression? = nil -} - -/// A description of a closure invocation. -/// -/// For example: `{ foo in return foo + "bar" }`. -struct ClosureInvocationDescription: Equatable, Codable { - - /// The names of the arguments taken by the closure. - /// - /// For example, in `{ foo in return foo + "bar" }`, `argumentNames` - /// is `["foo"]`. - var argumentNames: [String] = [] - - /// The code blocks of the closure body. - /// - /// For example, in `{ foo in return foo + "bar" }`, `body` - /// represents `return foo + "bar"`. - var body: [CodeBlock]? = nil -} - -/// A binary operator. -/// -/// For example: `+=` in `a += b`. -enum BinaryOperator: String, Equatable, Codable { - - /// The += operator, adds and then assigns another value. - case plusEquals = "+=" - - /// The == operator, checks equality between two values. - case equals = "==" - - /// The ... operator, creates an end-inclusive range between two numbers. - case rangeInclusive = "..." - - /// The || operator, used between two Boolean values. - case booleanOr = "||" -} - -/// A description of a binary operation expression. -/// -/// For example: `foo += 1`. -struct BinaryOperationDescription: Equatable, Codable { - - /// The left-hand side expression of the operation. - /// - /// For example, in `foo += 1`, `left` is `foo`. - var left: Expression - - /// The binary operator tying the two expressions together. - /// - /// For example, in `foo += 1`, `operation` represents `+=`. - var operation: BinaryOperator - - /// The right-hand side expression of the operation. - /// - /// For example, in `foo += 1`, `right` is `1`. - var right: Expression -} - -/// A description of an inout expression, which provides a read-write -/// reference to a variable. -/// -/// For example, `&foo` passes a reference to the `foo` variable. -struct InOutDescription: Equatable, Codable { - - /// The referenced expression. - /// - /// For example, in `&foo`, `referencedExpr` is `foo`. - var referencedExpr: Expression -} - -/// A description of an optional chaining expression. -/// -/// For example, in `foo?`, `referencedExpr` is `foo`. -struct OptionalChainingDescription: Equatable, Codable { - - /// The referenced expression. - /// - /// For example, in `foo?`, `referencedExpr` is `foo`. - var referencedExpr: Expression -} - -/// A description of a tuple. -/// -/// For example: `(foo, bar)`. -struct TupleDescription: Equatable, Codable { - - /// The member expressions. - /// - /// For example, in `(foo, bar)`, `members` is `[foo, bar]`. - var members: [Expression] -} - -/// A Swift expression. -indirect enum Expression: Equatable, Codable { - - /// A literal. - /// - /// For example `"hello"` or `42`. - case literal(LiteralDescription) - - /// An identifier, such as a variable name. - /// - /// For example, in `let foo = 42`, `foo` is an identifier. - case identifier(IdentifierDescription) - - /// A member access expression. - /// - /// For example: `foo.bar`. - case memberAccess(MemberAccessDescription) - - /// A function call. - /// - /// For example: `foo(bar: 42)`. - case functionCall(FunctionCallDescription) - - /// An assignment expression. - /// - /// For example `foo = 42`. - case assignment(AssignmentDescription) - - /// A switch statement expression. - /// - /// For example: `switch foo {`. - case `switch`(SwitchDescription) - - /// An if statement, with optional else if's and an else statement attached. - /// - /// For example: `if foo { bar } else if baz { boo } else { bam }`. - case ifStatement(IfStatementDescription) - - /// A do statement. - /// - /// For example: `do { try foo() } catch { return bar }`. - case doStatement(DoStatementDescription) - - /// A value binding used in enums with associated values. - /// - /// For example: `let foo(bar)`. - case valueBinding(ValueBindingDescription) - - /// An expression that places a keyword before an expression. - case unaryKeyword(UnaryKeywordDescription) - - /// A closure invocation. - /// - /// For example: `{ foo in return foo + "bar" }`. - case closureInvocation(ClosureInvocationDescription) - - /// A binary operation expression. - /// - /// For example: `foo += 1`. - case binaryOperation(BinaryOperationDescription) - - /// An inout expression, which provides a reference to a variable. - /// - /// For example, `&foo` passes a reference to the `foo` variable. - case inOut(InOutDescription) - - /// An optional chaining expression. - /// - /// For example, in `foo?`, `referencedExpr` is `foo`. - case optionalChaining(OptionalChainingDescription) - - /// A tuple expression. - /// - /// For example: `(foo, bar)`. - case tuple(TupleDescription) -} - -/// A code block item, either a declaration or an expression. -enum CodeBlockItem: Equatable, Codable { - - /// A declaration, such as of a new type or function. - case declaration(Declaration) - - /// An expression, such as a call of a declared function. - case expression(Expression) -} - -/// A code block, with an optional comment. -struct CodeBlock: Equatable, Codable { - - /// The comment to prepend to the code block item. - var comment: Comment? - - /// The code block item that appears below the comment. - var item: CodeBlockItem -} - -/// A description of a Swift file. -struct FileDescription: Equatable, Codable { - - /// A comment placed at the top of the file. - var topComment: Comment? - - /// Import statements placed below the top comment, but before the code - /// blocks. - var imports: [ImportDescription]? - - /// The code blocks that represent the main contents of the file. - var codeBlocks: [CodeBlock] -} - -/// A description of a named Swift file. -struct NamedFileDescription: Equatable, Codable { - - /// A file name, including the file extension. - /// - /// For example: `Foo.swift`. - var name: String - - /// The contents of the file. - var contents: FileDescription -} - -/// A file with contents made up of structured Swift code. -struct StructuredSwiftRepresentation: Equatable, Codable { - - /// The contents of the file. - var file: NamedFileDescription -} - -// MARK: - Conveniences - -extension Declaration { - - /// A variable declaration. - /// - /// For example: `let foo = 42`. - /// - Parameters: - /// - accessModifier: An access modifier. - /// - isStatic: A Boolean value that indicates whether the variable - /// is static. - /// - kind: The variable binding kind. - /// - left: The name of the variable. - /// - type: The type of the variable. - /// - right: The expression to be assigned to the variable. - /// - getter: Body code for the getter of the variable. - /// - getterEffects: Effects of the getter. - /// - setter: Body code for the setter of the variable. - /// - modify: Body code for the `_modify` accessor. - /// - Returns: Variable declaration. - static func variable( - accessModifier: AccessModifier? = nil, - isStatic: Bool = false, - kind: BindingKind, - left: String, - type: ExistingTypeDescription? = nil, - right: Expression? = nil, - getter: [CodeBlock]? = nil, - getterEffects: [FunctionKeyword] = [], - setter: [CodeBlock]? = nil, - modify: [CodeBlock]? = nil - - ) -> Self { - .variable( - accessModifier: accessModifier, - isStatic: isStatic, - kind: kind, - left: .identifierPattern(left), - type: type, - right: right, - getter: getter, - getterEffects: getterEffects, - setter: setter, - modify: modify - ) - } - - /// A variable declaration. - /// - /// For example: `let foo = 42`. - /// - Parameters: - /// - accessModifier: An access modifier. - /// - isStatic: A Boolean value that indicates whether the variable - /// is static. - /// - kind: The variable binding kind. - /// - left: The name of the variable. - /// - type: The type of the variable. - /// - right: The expression to be assigned to the variable. - /// - getter: Body code for the getter of the variable. - /// - getterEffects: Effects of the getter. - /// - setter: Body code for the setter of the variable. - /// - modify: Body code for the `_modify` accessor. - /// - Returns: Variable declaration. - static func variable( - accessModifier: AccessModifier? = nil, - isStatic: Bool = false, - kind: BindingKind, - left: Expression, - type: ExistingTypeDescription? = nil, - right: Expression? = nil, - getter: [CodeBlock]? = nil, - getterEffects: [FunctionKeyword] = [], - setter: [CodeBlock]? = nil, - modify: [CodeBlock]? = nil - - ) -> Self { - .variable( - .init( - accessModifier: accessModifier, - isStatic: isStatic, - kind: kind, - left: left, - type: type, - right: right, - getter: getter, - getterEffects: getterEffects, - setter: setter, - modify: modify - ) - ) - } - - /// A description of an enum case. - /// - /// For example: `case foo(String)`. - /// - Parameters: - /// - name: The name of the enum case. - /// - kind: The kind of the enum case. - /// - Returns: An enum case declaration. - static func enumCase(name: String, kind: EnumCaseKind = .nameOnly) -> Self { - .enumCase(.init(name: name, kind: kind)) - } - - /// A description of a typealias declaration. - /// - /// For example `typealias Foo = Int`. - /// - Parameters: - /// - accessModifier: An access modifier. - /// - name: The name of the typealias. - /// - existingType: The existing type that serves as the - /// underlying type of the alias. - /// - Returns: A typealias declaration. - static func `typealias`( - accessModifier: AccessModifier? = nil, - name: String, - existingType: ExistingTypeDescription - ) - -> Self - { .typealias(.init(accessModifier: accessModifier, name: name, existingType: existingType)) } - - /// A description of a function definition. - /// - /// For example: `func foo() { }`. - /// - Parameters: - /// - accessModifier: An access modifier. - /// - kind: The kind of the function. - /// - parameters: The parameters of the function. - /// - keywords: The keywords of the function, such as `async` and - /// `throws.` - /// - returnType: The return type name of the function, such as `Int`. - /// - body: The body definition of the function. - /// - Returns: A function declaration. - static func function( - accessModifier: AccessModifier? = nil, - kind: FunctionKind, - generics: [ExistingTypeDescription] = [], - parameters: [ParameterDescription], - keywords: [FunctionKeyword] = [], - returnType: Expression? = nil, - whereClause: WhereClause?, - body: [CodeBlock]? = nil - ) -> Self { - .function( - .init( - accessModifier: accessModifier, - kind: kind, - generics: generics, - parameters: parameters, - keywords: keywords, - returnType: returnType, - whereClause: whereClause, - body: body - ) - ) - } - - /// A description of a function definition. - /// - /// For example: `func foo() { }`. - /// - Parameters: - /// - signature: The signature of the function. - /// - body: The body definition of the function. - /// - Returns: A function declaration. - static func function(signature: FunctionSignatureDescription, body: [CodeBlock]? = nil) -> Self { - .function(.init(signature: signature, body: body)) - } - - /// A description of an enum declaration. - /// - /// For example `enum Bar {`. - /// - Parameters: - /// - isFrozen: A Boolean value that indicates whether the enum has - /// a `@frozen` attribute. - /// - accessModifier: An access modifier. - /// - name: The name of the enum. - /// - conformances: The type names that the enum conforms to. - /// - members: The declarations that make up the enum body. - /// - Returns: An enum declaration. - static func `enum`( - isFrozen: Bool = false, - accessModifier: AccessModifier? = nil, - name: String, - conformances: [String] = [], - members: [Declaration] = [] - ) -> Self { - .enum( - .init( - isFrozen: isFrozen, - accessModifier: accessModifier, - name: name, - conformances: conformances, - members: members - ) - ) - } - - /// A description of an extension declaration. - /// - /// For example `extension Foo {`. - /// - Parameters: - /// - accessModifier: An access modifier. - /// - onType: The name of the extended type. - /// - conformances: Additional type names that the extension conforms to. - /// - whereClause: A where clause constraining the extension declaration. - /// - declarations: The declarations that the extension adds on the - /// extended type. - /// - Returns: An extension declaration. - static func `extension`( - accessModifier: AccessModifier? = nil, - onType: String, - conformances: [String] = [], - whereClause: WhereClause? = nil, - declarations: [Declaration] - ) -> Self { - .extension( - .init( - accessModifier: accessModifier, - onType: onType, - conformances: conformances, - whereClause: whereClause, - declarations: declarations - ) - ) - } -} - -extension FunctionKind { - /// Returns a non-failable initializer, for example `init()`. - static var initializer: Self { .initializer(failable: false) } - - /// Returns a non-static function kind. - static func function(name: String) -> Self { - .function(name: name, isStatic: false) - } -} - -extension CodeBlock { - - /// Returns a new declaration code block wrapping the provided declaration. - /// - Parameter declaration: The declaration to wrap. - /// - Returns: A new `CodeBlock` instance containing the provided declaration. - static func declaration(_ declaration: Declaration) -> Self { - CodeBlock(item: .declaration(declaration)) - } - - /// Returns a new expression code block wrapping the provided expression. - /// - Parameter expression: The expression to wrap. - /// - Returns: A new `CodeBlock` instance containing the provided declaration. - static func expression(_ expression: Expression) -> Self { - CodeBlock(item: .expression(expression)) - } -} - -extension Expression { - - /// A string literal. - /// - /// For example: `"hello"`. - /// - Parameter value: The string value of the literal. - /// - Returns: A new `Expression` representing a string literal. - static func literal(_ value: String) -> Self { .literal(.string(value)) } - - /// An integer literal. - /// - /// For example `42`. - /// - Parameter value: The integer value of the literal. - /// - Returns: A new `Expression` representing an integer literal. - static func literal(_ value: Int) -> Self { .literal(.int(value)) } - - /// Returns a new expression that accesses the member on the current - /// expression. - /// - Parameter member: The name of the member to access on the expression. - /// - Returns: A new expression representing member access. - func dot(_ member: String) -> Expression { .memberAccess(.init(left: self, right: member)) } - - /// Returns a new expression that calls the current expression as a function - /// with the specified arguments. - /// - Parameter arguments: The arguments used to call the expression. - /// - Returns: A new expression representing a function call. - func call(_ arguments: [FunctionArgumentDescription]) -> Expression { - .functionCall(.init(calledExpression: self, arguments: arguments)) - } - - /// Returns a new member access expression without a receiver, - /// starting with dot. - /// - /// For example: `.foo`, where `member` is `foo`. - /// - Parameter member: The name of the member to access. - /// - Returns: A new expression representing member access with a dot prefix. - static func dot(_ member: String) -> Self { Self.memberAccess(.init(right: member)) } - - /// Returns a new identifier expression for the provided pattern, such - /// as a variable or function name. - /// - Parameter name: The name of the identifier. - /// - Returns: A new expression representing an identifier with - /// the specified name. - static func identifierPattern(_ name: String) -> Self { .identifier(.pattern(name)) } - - /// Returns a new identifier expression for the provided type name. - /// - Parameter type: The description of the type. - /// - Returns: A new expression representing an identifier with - /// the specified name. - static func identifierType(_ type: ExistingTypeDescription) -> Self { .identifier(.type(type)) } - - /// Returns a new identifier expression for the provided type name. - /// - Parameter type: The name of the type. - /// - Returns: A new expression representing an identifier with - /// the specified name. - static func identifierType(_ type: TypeName) -> Self { .identifier(.type(.init(type))) } - - /// Returns a new identifier expression for the provided type name. - /// - Parameter type: The usage of the type. - /// - Returns: A new expression representing an identifier with - /// the specified name. - static func identifierType(_ type: TypeUsage) -> Self { .identifier(.type(.init(type))) } - - /// Returns a new switch statement expression. - /// - Parameters: - /// - switchedExpression: The expression evaluated by the switch - /// statement. - /// - cases: The cases defined in the switch statement. - /// - Returns: A new expression representing a switch statement with the specified switched expression and cases - static func `switch`(switchedExpression: Expression, cases: [SwitchCaseDescription]) -> Self { - .`switch`(.init(switchedExpression: switchedExpression, cases: cases)) - } - - /// Returns an if statement, with optional else if's and an else - /// statement attached. - /// - Parameters: - /// - ifBranch: The primary `if` branch. - /// - elseIfBranches: Additional `else if` branches. - /// - elseBody: The body of an else block. - /// - Returns: A new expression representing an `if` statement with the specified branches and blocks. - static func ifStatement( - ifBranch: IfBranch, - elseIfBranches: [IfBranch] = [], - elseBody: [CodeBlock]? = nil - ) -> Self { - .ifStatement(.init(ifBranch: ifBranch, elseIfBranches: elseIfBranches, elseBody: elseBody)) - } - - /// Returns a new function call expression. - /// - /// For example `foo(bar: 42)`. - /// - Parameters: - /// - calledExpression: The expression to be called as a function. - /// - arguments: The arguments to be passed to the function call. - /// - trailingClosure: A trailing closure. - /// - Returns: A new expression representing a function call with the specified called expression and arguments. - static func functionCall( - calledExpression: Expression, - arguments: [FunctionArgumentDescription] = [], - trailingClosure: ClosureInvocationDescription? = nil - ) -> Self { - .functionCall( - .init( - calledExpression: calledExpression, - arguments: arguments, - trailingClosure: trailingClosure - ) - ) - } - - /// Returns a new function call expression. - /// - /// For example: `foo(bar: 42)`. - /// - Parameters: - /// - calledExpression: The expression called as a function. - /// - arguments: The arguments passed to the function call. - /// - trailingClosure: A trailing closure. - /// - Returns: A new expression representing a function call with the specified called expression and arguments. - static func functionCall( - calledExpression: Expression, - arguments: [Expression], - trailingClosure: ClosureInvocationDescription? = nil - ) -> Self { - .functionCall( - .init( - calledExpression: calledExpression, - arguments: arguments.map { .init(label: nil, expression: $0) }, - trailingClosure: trailingClosure - ) - ) - } - - /// Returns a new expression that places a keyword before an expression. - /// - Parameters: - /// - kind: The keyword to place before the expression. - /// - expression: The expression prefixed by the keyword. - /// - Returns: A new expression with the specified keyword placed before the expression. - static func unaryKeyword(kind: KeywordKind, expression: Expression? = nil) -> Self { - .unaryKeyword(.init(kind: kind, expression: expression)) - } - - /// Returns a new expression that puts the return keyword before - /// an expression. - /// - Parameter expression: The expression to prepend. - /// - Returns: A new expression with the `return` keyword placed before the expression. - static func `return`(_ expression: Expression? = nil) -> Self { - .unaryKeyword(kind: .return, expression: expression) - } - - /// Returns a new expression that puts the try keyword before - /// an expression. - /// - Parameter expression: The expression to prepend. - /// - Returns: A new expression with the `try` keyword placed before the expression. - static func `try`(_ expression: Expression) -> Self { - .unaryKeyword(kind: .try, expression: expression) - } - - /// Returns a new expression that puts the try? keyword before - /// an expression. - /// - Parameter expression: The expression to prepend. - /// - Returns: A new expression with the `try?` keyword placed before the expression. - static func optionalTry(_ expression: Expression) -> Self { - .unaryKeyword(kind: .try(hasPostfixQuestionMark: true), expression: expression) - } - - /// Returns a new expression that puts the await keyword before - /// an expression. - /// - Parameter expression: The expression to prepend. - /// - Returns: A new expression with the `await` keyword placed before the expression. - static func `await`(_ expression: Expression) -> Self { - .unaryKeyword(kind: .await, expression: expression) - } - - /// Returns a new expression that puts the yield keyword before - /// an expression. - /// - Parameter expression: The expression to prepend. - /// - Returns: A new expression with the `yield` keyword placed before the expression. - static func `yield`(_ expression: Expression) -> Self { - .unaryKeyword(kind: .yield, expression: expression) - } - - /// Returns a new expression that puts the provided code blocks into - /// a do/catch block. - /// - Parameter: - /// - doStatement: The code blocks in the `do` statement body. - /// - catchBody: The code blocks in the `catch` statement. - /// - Returns: The expression. - static func `do`(_ doStatement: [CodeBlock], catchBody: [CodeBlock]? = nil) -> Self { - .doStatement(.init(doStatement: doStatement, catchBody: catchBody)) - } - - /// Returns a new value binding used in enums with associated values. - /// - /// For example: `let foo(bar)`. - /// - Parameters: - /// - kind: The binding kind: `let` or `var`. - /// - value: The bound values in a function call expression syntax. - /// - Returns: A new expression representing the value binding. - static func valueBinding(kind: BindingKind, value: FunctionCallDescription) -> Self { - .valueBinding(.init(kind: kind, value: value)) - } - - /// Returns a new closure invocation expression. - /// - /// For example: such as `{ foo in return foo + "bar" }`. - /// - Parameters: - /// - argumentNames: The names of the arguments taken by the closure. - /// - body: The code blocks of the closure body. - /// - Returns: A new expression representing the closure invocation - static func `closureInvocation`(argumentNames: [String] = [], body: [CodeBlock]? = nil) -> Self { - .closureInvocation(.init(argumentNames: argumentNames, body: body)) - } - - /// Creates a new binary operation expression. - /// - /// For example: `foo += 1`. - /// - Parameters: - /// - left: The left-hand side expression of the operation. - /// - operation: The binary operator tying the two expressions together. - /// - right: The right-hand side expression of the operation. - /// - Returns: A new expression representing the binary operation. - static func `binaryOperation`( - left: Expression, - operation: BinaryOperator, - right: Expression - ) -> Self { - .binaryOperation(.init(left: left, operation: operation, right: right)) - } - - /// Creates a new inout expression, which provides a read-write - /// reference to a variable. - /// - /// For example, `&foo` passes a reference to the `foo` variable. - /// - Parameter referencedExpr: The referenced expression. - /// - Returns: A new expression representing the inout expression. - static func inOut(_ referencedExpr: Expression) -> Self { - .inOut(.init(referencedExpr: referencedExpr)) - } - - /// Creates a new assignment expression. - /// - /// For example: `foo = 42`. - /// - Parameters: - /// - left: The left-hand side expression, the variable to assign to. - /// - right: The right-hand side expression, the value to assign. - /// - Returns: Assignment expression. - static func assignment(left: Expression, right: Expression) -> Self { - .assignment(.init(left: left, right: right)) - } - - /// Returns a new optional chaining expression wrapping the current - /// expression. - /// - /// For example, for the current expression `foo`, returns `foo?`. - /// - Returns: A new expression representing the optional chaining operation. - func optionallyChained() -> Self { .optionalChaining(.init(referencedExpr: self)) } - - /// Returns a new tuple expression. - /// - /// For example, in `(foo, bar)`, `members` is `[foo, bar]`. - /// - Parameter expressions: The member expressions. - /// - Returns: A tuple expression. - static func tuple(_ expressions: [Expression]) -> Self { .tuple(.init(members: expressions)) } -} - -extension MemberAccessDescription { - /// Creates a new member access expression without a receiver, starting - /// with dot. - /// - /// For example, `.foo`, where `member` is `foo`. - /// - Parameter member: The name of the member to access. - /// - Returns: A new member access expression. - static func dot(_ member: String) -> Self { .init(right: member) } -} - -extension LiteralDescription: ExpressibleByStringLiteral, ExpressibleByNilLiteral, - ExpressibleByArrayLiteral -{ - init(arrayLiteral elements: Expression...) { self = .array(elements) } - - init(stringLiteral value: String) { self = .string(value) } - - init(nilLiteral: ()) { self = .nil } -} - -extension VariableDescription { - - /// Returns a new mutable variable declaration. - /// - /// For example `var foo = 42`. - /// - Parameter name: The name of the variable. - /// - Returns: A new mutable variable declaration. - static func `var`(_ name: String) -> Self { - Self.init(kind: .var, left: .identifierPattern(name)) - } - - /// Returns a new immutable variable declaration. - /// - /// For example `let foo = 42`. - /// - Parameter name: The name of the variable. - /// - Returns: A new immutable variable declaration. - static func `let`(_ name: String) -> Self { - Self.init(kind: .let, left: .identifierPattern(name)) - } -} - -extension Expression { - - /// Creates a new assignment description where the called expression is - /// assigned the value of the specified expression. - /// - Parameter rhs: The right-hand side of the assignment expression. - /// - Returns: An assignment description representing the assignment. - func equals(_ rhs: Expression) -> AssignmentDescription { .init(left: self, right: rhs) } -} - -extension FunctionSignatureDescription { - /// Returns a new function signature description that has the access - /// modifier updated to the specified one. - /// - Parameter accessModifier: The access modifier to use. - /// - Returns: A function signature description with the specified access modifier. - func withAccessModifier(_ accessModifier: AccessModifier?) -> Self { - var value = self - value.accessModifier = accessModifier - return value - } -} - -extension SwitchCaseKind { - /// Returns a new switch case kind with no argument names, only the - /// specified expression as the name. - /// - Parameter expression: The expression for the switch case label. - /// - Returns: A switch case kind with the specified expression as the label. - static func `case`(_ expression: Expression) -> Self { .case(expression, []) } -} - -extension KeywordKind { - - /// Returns the try keyword without the postfix question mark. - static var `try`: Self { .try(hasPostfixQuestionMark: false) } -} - -extension Declaration { - /// Returns a new deprecated variant of the declaration if `shouldDeprecate` is true. - func deprecate(if shouldDeprecate: Bool) -> Self { - if shouldDeprecate { return .deprecated(.init(), self) } - return self - } - - /// Returns the declaration one level deeper, nested inside the commentable - /// declaration, if present. - var strippingTopComment: Self { - guard case let .commentable(_, underlyingDecl) = self else { return self } - return underlyingDecl - } -} - -extension Declaration { - - /// An access modifier. - var accessModifier: AccessModifier? { - get { - switch self { - case .commentable(_, let declaration): return declaration.accessModifier - case .deprecated(_, let declaration): return declaration.accessModifier - case .guarded(_, let declaration): return declaration.accessModifier - case .variable(let variableDescription): return variableDescription.accessModifier - case .extension(let extensionDescription): return extensionDescription.accessModifier - case .struct(let structDescription): return structDescription.accessModifier - case .enum(let enumDescription): return enumDescription.accessModifier - case .typealias(let typealiasDescription): return typealiasDescription.accessModifier - case .protocol(let protocolDescription): return protocolDescription.accessModifier - case .function(let functionDescription): return functionDescription.signature.accessModifier - case .enumCase: return nil - } - } - set { - switch self { - case .commentable(let comment, var declaration): - declaration.accessModifier = newValue - self = .commentable(comment, declaration) - case .deprecated(let deprecationDescription, var declaration): - declaration.accessModifier = newValue - self = .deprecated(deprecationDescription, declaration) - case .guarded(let availability, var declaration): - declaration.accessModifier = newValue - self = .guarded(availability, declaration) - case .variable(var variableDescription): - variableDescription.accessModifier = newValue - self = .variable(variableDescription) - case .extension(var extensionDescription): - extensionDescription.accessModifier = newValue - self = .extension(extensionDescription) - case .struct(var structDescription): - structDescription.accessModifier = newValue - self = .struct(structDescription) - case .enum(var enumDescription): - enumDescription.accessModifier = newValue - self = .enum(enumDescription) - case .typealias(var typealiasDescription): - typealiasDescription.accessModifier = newValue - self = .typealias(typealiasDescription) - case .protocol(var protocolDescription): - protocolDescription.accessModifier = newValue - self = .protocol(protocolDescription) - case .function(var functionDescription): - functionDescription.signature.accessModifier = newValue - self = .function(functionDescription) - case .enumCase: break - } - } - } -} - -extension ExistingTypeDescription { - - /// Creates a member type description with the provided single component. - /// - Parameter singleComponent: A single component of the name. - /// - Returns: The new type description. - static func member(_ singleComponent: String) -> Self { .member([singleComponent]) } -} diff --git a/Sources/GRPCCodeGen/Internal/Translator/ClientCodeTranslator.swift b/Sources/GRPCCodeGen/Internal/Translator/ClientCodeTranslator.swift deleted file mode 100644 index 5ead61a67..000000000 --- a/Sources/GRPCCodeGen/Internal/Translator/ClientCodeTranslator.swift +++ /dev/null @@ -1,697 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -/// Creates a representation for the client code that will be generated based on the ``CodeGenerationRequest`` object -/// specifications, using types from ``StructuredSwiftRepresentation``. -/// -/// For example, in the case of a service called "Bar", in the "foo" namespace which has -/// one method "baz" with input type "Input" and output type "Output", the ``ClientCodeTranslator`` will create -/// a representation for the following generated code: -/// -/// ```swift -/// @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -/// public protocol Foo_BarClientProtocol: Sendable { -/// func baz( -/// request: GRPCCore.ClientRequest.Single, -/// serializer: some GRPCCore.MessageSerializer, -/// deserializer: some GRPCCore.MessageDeserializer, -/// options: GRPCCore.CallOptions = .defaults, -/// _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R -/// ) async throws -> R where R: Sendable -/// } -/// @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -/// extension Foo_Bar.ClientProtocol { -/// public func baz( -/// request: GRPCCore.ClientRequest.Single, -/// options: GRPCCore.CallOptions = .defaults, -/// _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R = { -/// try $0.message -/// } -/// ) async throws -> R where R: Sendable { -/// try await self.baz( -/// request: request, -/// serializer: GRPCProtobuf.ProtobufSerializer(), -/// deserializer: GRPCProtobuf.ProtobufDeserializer(), -/// options: options, -/// body -/// ) -/// } -/// @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -/// public struct Foo_BarClient: Foo_Bar.ClientProtocol { -/// private let client: GRPCCore.GRPCClient -/// public init(wrapping client: GRPCCore.GRPCClient) { -/// self.client = client -/// } -/// public func methodA( -/// request: GRPCCore.ClientRequest.Stream, -/// serializer: some GRPCCore.MessageSerializer, -/// deserializer: some GRPCCore.MessageDeserializer, -/// options: GRPCCore.CallOptions = .defaults, -/// _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R = { -/// try $0.message -/// } -/// ) async throws -> R where R: Sendable { -/// try await self.client.unary( -/// request: request, -/// descriptor: NamespaceA.ServiceA.Method.MethodA.descriptor, -/// serializer: serializer, -/// deserializer: deserializer, -/// options: options, -/// handler: body -/// ) -/// } -/// } -///``` -struct ClientCodeTranslator: SpecializedTranslator { - var accessLevel: SourceGenerator.Config.AccessLevel - - init(accessLevel: SourceGenerator.Config.AccessLevel) { - self.accessLevel = accessLevel - } - - func translate(from request: CodeGenerationRequest) throws -> [CodeBlock] { - var blocks = [CodeBlock]() - - for service in request.services { - let `protocol` = self.makeClientProtocol(for: service, in: request) - blocks.append(.declaration(.commentable(.preFormatted(service.documentation), `protocol`))) - - let defaultImplementation = self.makeDefaultImplementation(for: service, in: request) - blocks.append(.declaration(defaultImplementation)) - - let sugaredAPI = self.makeSugaredAPI(forService: service, request: request) - blocks.append(.declaration(sugaredAPI)) - - let clientStruct = self.makeClientStruct(for: service, in: request) - blocks.append(.declaration(.commentable(.preFormatted(service.documentation), clientStruct))) - } - - return blocks - } -} - -extension ClientCodeTranslator { - private func makeClientProtocol( - for service: CodeGenerationRequest.ServiceDescriptor, - in codeGenerationRequest: CodeGenerationRequest - ) -> Declaration { - let methods = service.methods.map { - self.makeClientProtocolMethod( - for: $0, - in: service, - from: codeGenerationRequest, - includeBody: false, - includeDefaultCallOptions: false - ) - } - - let clientProtocol = Declaration.protocol( - ProtocolDescription( - accessModifier: self.accessModifier, - name: "\(service.namespacedGeneratedName)ClientProtocol", - conformances: ["Sendable"], - members: methods - ) - ) - return .guarded(self.availabilityGuard, clientProtocol) - } - - private func makeDefaultImplementation( - for service: CodeGenerationRequest.ServiceDescriptor, - in codeGenerationRequest: CodeGenerationRequest - ) -> Declaration { - let methods = service.methods.map { - self.makeClientProtocolMethod( - for: $0, - in: service, - from: codeGenerationRequest, - includeBody: true, - accessModifier: self.accessModifier, - includeDefaultCallOptions: true - ) - } - let clientProtocolExtension = Declaration.extension( - ExtensionDescription( - onType: "\(service.namespacedGeneratedName).ClientProtocol", - declarations: methods - ) - ) - return .guarded( - self.availabilityGuard, - clientProtocolExtension - ) - } - - private func makeSugaredAPI( - forService service: CodeGenerationRequest.ServiceDescriptor, - request: CodeGenerationRequest - ) -> Declaration { - let sugaredAPIExtension = Declaration.extension( - ExtensionDescription( - onType: "\(service.namespacedGeneratedName).ClientProtocol", - declarations: service.methods.map { method in - self.makeSugaredMethodDeclaration( - method: method, - accessModifier: self.accessModifier - ) - } - ) - ) - - return .guarded(self.availabilityGuard, sugaredAPIExtension) - } - - private func makeSugaredMethodDeclaration( - method: CodeGenerationRequest.ServiceDescriptor.MethodDescriptor, - accessModifier: AccessModifier? - ) -> Declaration { - let signature = FunctionSignatureDescription( - accessModifier: accessModifier, - kind: .function(name: method.name.generatedLowerCase), - generics: [.member("Result")], - parameters: self.makeParametersForSugaredMethodDeclaration(method: method), - keywords: [.async, .throws], - returnType: .identifierPattern("Result"), - whereClause: WhereClause( - requirements: [ - .conformance("Result", "Sendable") - ] - ) - ) - - let functionDescription = FunctionDescription( - signature: signature, - body: self.makeFunctionBodyForSugaredMethodDeclaration(method: method) - ) - - if method.documentation.isEmpty { - return .function(functionDescription) - } else { - return .commentable(.preFormatted(method.documentation), .function(functionDescription)) - } - } - - private func makeParametersForSugaredMethodDeclaration( - method: CodeGenerationRequest.ServiceDescriptor.MethodDescriptor - ) -> [ParameterDescription] { - var parameters = [ParameterDescription]() - - // Unary inputs have a 'message' parameter - if !method.isInputStreaming { - parameters.append( - ParameterDescription( - label: "_", - name: "message", - type: .member([method.inputType]) - ) - ) - } - - parameters.append( - ParameterDescription( - label: "metadata", - type: .member(["GRPCCore", "Metadata"]), - defaultValue: .literal(.dictionary([])) - ) - ) - - parameters.append( - ParameterDescription( - label: "options", - type: .member(["GRPCCore", "CallOptions"]), - defaultValue: .memberAccess(.dot("defaults")) - ) - ) - - // Streaming inputs have a writer callback - if method.isInputStreaming { - parameters.append( - ParameterDescription( - label: "requestProducer", - type: .closure( - ClosureSignatureDescription( - parameters: [ - ParameterDescription( - type: .generic( - wrapper: .member(["GRPCCore", "RPCWriter"]), - wrapped: .member(method.inputType) - ) - ) - ], - keywords: [.async, .throws], - returnType: .identifierPattern("Void"), - sendable: true, - escaping: true - ) - ) - ) - ) - } - - // All methods have a response handler. - var responseHandler = ParameterDescription(label: "onResponse", name: "handleResponse") - let responseKind = method.isOutputStreaming ? "Stream" : "Single" - responseHandler.type = .closure( - ClosureSignatureDescription( - parameters: [ - ParameterDescription( - type: .generic( - wrapper: .member(["GRPCCore", "ClientResponse", responseKind]), - wrapped: .member(method.outputType) - ) - ) - ], - keywords: [.async, .throws], - returnType: .identifierPattern("Result"), - sendable: true, - escaping: true - ) - ) - - if !method.isOutputStreaming { - responseHandler.defaultValue = .closureInvocation( - ClosureInvocationDescription( - body: [.expression(.try(.identifierPattern("$0").dot("message")))] - ) - ) - } - - parameters.append(responseHandler) - - return parameters - } - - private func makeFunctionBodyForSugaredMethodDeclaration( - method: CodeGenerationRequest.ServiceDescriptor.MethodDescriptor - ) -> [CodeBlock] { - // Produces the following: - // - // let request = GRPCCore.ClientRequest.Single(message: message, metadata: metadata) - // return try await method(request: request, options: options, responseHandler: responseHandler) - // - // or: - // - // let request = GRPCCore.ClientRequest.Stream(metadata: metadata, producer: writer) - // return try await method(request: request, options: options, responseHandler: responseHandler) - - // First, make the init for the ClientRequest - let requestType = method.isInputStreaming ? "Stream" : "Single" - var requestInit = FunctionCallDescription( - calledExpression: .identifier( - .type( - .generic( - wrapper: .member(["GRPCCore", "ClientRequest", requestType]), - wrapped: .member(method.inputType) - ) - ) - ) - ) - - if method.isInputStreaming { - requestInit.arguments.append( - FunctionArgumentDescription( - label: "metadata", - expression: .identifierPattern("metadata") - ) - ) - requestInit.arguments.append( - FunctionArgumentDescription( - label: "producer", - expression: .identifierPattern("requestProducer") - ) - ) - } else { - requestInit.arguments.append( - FunctionArgumentDescription( - label: "message", - expression: .identifierPattern("message") - ) - ) - requestInit.arguments.append( - FunctionArgumentDescription( - label: "metadata", - expression: .identifierPattern("metadata") - ) - ) - } - - // Now declare the request: - // - // let request = - let request = VariableDescription( - kind: .let, - left: .identifier(.pattern("request")), - right: .functionCall(requestInit) - ) - - var blocks = [CodeBlock]() - blocks.append(.declaration(.variable(request))) - - // Finally, call the underlying method. - let methodCall = FunctionCallDescription( - calledExpression: .identifierPattern("self").dot(method.name.generatedLowerCase), - arguments: [ - FunctionArgumentDescription(label: "request", expression: .identifierPattern("request")), - FunctionArgumentDescription(label: "options", expression: .identifierPattern("options")), - FunctionArgumentDescription(expression: .identifierPattern("handleResponse")), - ] - ) - - blocks.append(.expression(.return(.try(.await(.functionCall(methodCall)))))) - return blocks - } - - private func makeClientProtocolMethod( - for method: CodeGenerationRequest.ServiceDescriptor.MethodDescriptor, - in service: CodeGenerationRequest.ServiceDescriptor, - from codeGenerationRequest: CodeGenerationRequest, - includeBody: Bool, - accessModifier: AccessModifier? = nil, - includeDefaultCallOptions: Bool - ) -> Declaration { - let isProtocolExtension = includeBody - let methodParameters = self.makeParameters( - for: method, - in: service, - from: codeGenerationRequest, - // The serializer/deserializer for the protocol extension method will be auto-generated. - includeSerializationParameters: !isProtocolExtension, - includeDefaultCallOptions: includeDefaultCallOptions, - includeDefaultResponseHandler: isProtocolExtension && !method.isOutputStreaming - ) - let functionSignature = FunctionSignatureDescription( - accessModifier: accessModifier, - kind: .function( - name: method.name.generatedLowerCase, - isStatic: false - ), - generics: [.member("R")], - parameters: methodParameters, - keywords: [.async, .throws], - returnType: .identifierType(.member("R")), - whereClause: WhereClause(requirements: [.conformance("R", "Sendable")]) - ) - - if includeBody { - let body = self.makeClientProtocolMethodCall( - for: method, - in: service, - from: codeGenerationRequest - ) - return .function(signature: functionSignature, body: body) - } else { - return .commentable( - .preFormatted(method.documentation), - .function(signature: functionSignature) - ) - } - } - - private func makeClientProtocolMethodCall( - for method: CodeGenerationRequest.ServiceDescriptor.MethodDescriptor, - in service: CodeGenerationRequest.ServiceDescriptor, - from codeGenerationRequest: CodeGenerationRequest - ) -> [CodeBlock] { - let functionCall = Expression.functionCall( - calledExpression: .memberAccess( - MemberAccessDescription( - left: .identifierPattern("self"), - right: method.name.generatedLowerCase - ) - ), - arguments: [ - FunctionArgumentDescription(label: "request", expression: .identifierPattern("request")), - FunctionArgumentDescription( - label: "serializer", - expression: .identifierPattern(codeGenerationRequest.lookupSerializer(method.inputType)) - ), - FunctionArgumentDescription( - label: "deserializer", - expression: .identifierPattern( - codeGenerationRequest.lookupDeserializer(method.outputType) - ) - ), - FunctionArgumentDescription(label: "options", expression: .identifierPattern("options")), - FunctionArgumentDescription(expression: .identifierPattern("body")), - ] - ) - let awaitFunctionCall = Expression.unaryKeyword(kind: .await, expression: functionCall) - let tryAwaitFunctionCall = Expression.unaryKeyword(kind: .try, expression: awaitFunctionCall) - - return [CodeBlock(item: .expression(tryAwaitFunctionCall))] - } - - private func makeParameters( - for method: CodeGenerationRequest.ServiceDescriptor.MethodDescriptor, - in service: CodeGenerationRequest.ServiceDescriptor, - from codeGenerationRequest: CodeGenerationRequest, - includeSerializationParameters: Bool, - includeDefaultCallOptions: Bool, - includeDefaultResponseHandler: Bool - ) -> [ParameterDescription] { - var parameters = [ParameterDescription]() - - parameters.append(self.clientRequestParameter(for: method, in: service)) - if includeSerializationParameters { - parameters.append(self.serializerParameter(for: method, in: service)) - parameters.append(self.deserializerParameter(for: method, in: service)) - } - parameters.append( - ParameterDescription( - label: "options", - type: .member(["GRPCCore", "CallOptions"]), - defaultValue: includeDefaultCallOptions - ? .memberAccess(MemberAccessDescription(right: "defaults")) : nil - ) - ) - parameters.append( - self.bodyParameter( - for: method, - in: service, - includeDefaultResponseHandler: includeDefaultResponseHandler - ) - ) - return parameters - } - private func clientRequestParameter( - for method: CodeGenerationRequest.ServiceDescriptor.MethodDescriptor, - in service: CodeGenerationRequest.ServiceDescriptor - ) -> ParameterDescription { - let requestType = method.isInputStreaming ? "Stream" : "Single" - let clientRequestType = ExistingTypeDescription.member([ - "GRPCCore", "ClientRequest", requestType, - ]) - return ParameterDescription( - label: "request", - type: .generic( - wrapper: clientRequestType, - wrapped: .member(method.inputType) - ) - ) - } - - private func serializerParameter( - for method: CodeGenerationRequest.ServiceDescriptor.MethodDescriptor, - in service: CodeGenerationRequest.ServiceDescriptor - ) -> ParameterDescription { - return ParameterDescription( - label: "serializer", - type: ExistingTypeDescription.some( - .generic( - wrapper: .member(["GRPCCore", "MessageSerializer"]), - wrapped: .member(method.inputType) - ) - ) - ) - } - - private func deserializerParameter( - for method: CodeGenerationRequest.ServiceDescriptor.MethodDescriptor, - in service: CodeGenerationRequest.ServiceDescriptor - ) -> ParameterDescription { - return ParameterDescription( - label: "deserializer", - type: ExistingTypeDescription.some( - .generic( - wrapper: .member(["GRPCCore", "MessageDeserializer"]), - wrapped: .member(method.outputType) - ) - ) - ) - } - - private func bodyParameter( - for method: CodeGenerationRequest.ServiceDescriptor.MethodDescriptor, - in service: CodeGenerationRequest.ServiceDescriptor, - includeDefaultResponseHandler: Bool - ) -> ParameterDescription { - let clientStreaming = method.isOutputStreaming ? "Stream" : "Single" - let closureParameterType = ExistingTypeDescription.generic( - wrapper: .member(["GRPCCore", "ClientResponse", clientStreaming]), - wrapped: .member(method.outputType) - ) - - let bodyClosure = ClosureSignatureDescription( - parameters: [.init(type: closureParameterType)], - keywords: [.async, .throws], - returnType: .identifierType(.member("R")), - sendable: true, - escaping: true - ) - - var defaultResponseHandler: Expression? = nil - - if includeDefaultResponseHandler { - defaultResponseHandler = .closureInvocation( - ClosureInvocationDescription( - body: [.expression(.try(.identifierPattern("$0").dot("message")))] - ) - ) - } - - return ParameterDescription( - name: "body", - type: .closure(bodyClosure), - defaultValue: defaultResponseHandler - ) - } - - private func makeClientStruct( - for service: CodeGenerationRequest.ServiceDescriptor, - in codeGenerationRequest: CodeGenerationRequest - ) -> Declaration { - let clientProperty = Declaration.variable( - accessModifier: .private, - kind: .let, - left: "client", - type: .member(["GRPCCore", "GRPCClient"]) - ) - let initializer = self.makeClientVariable() - let methods = service.methods.map { - Declaration.commentable( - .preFormatted($0.documentation), - self.makeClientMethod(for: $0, in: service, from: codeGenerationRequest) - ) - } - - return .guarded( - self.availabilityGuard, - .struct( - StructDescription( - accessModifier: self.accessModifier, - name: "\(service.namespacedGeneratedName)Client", - conformances: ["\(service.namespacedGeneratedName).ClientProtocol"], - members: [clientProperty, initializer] + methods - ) - ) - ) - } - - private func makeClientVariable() -> Declaration { - let initializerBody = Expression.assignment( - left: .memberAccess( - MemberAccessDescription(left: .identifierPattern("self"), right: "client") - ), - right: .identifierPattern("client") - ) - return .function( - signature: .init( - accessModifier: self.accessModifier, - kind: .initializer, - parameters: [ - .init(label: "wrapping", name: "client", type: .member(["GRPCCore", "GRPCClient"])) - ] - ), - body: [CodeBlock(item: .expression(initializerBody))] - ) - } - - private func clientMethod( - isInputStreaming: Bool, - isOutputStreaming: Bool - ) -> String { - switch (isInputStreaming, isOutputStreaming) { - case (true, true): - return "bidirectionalStreaming" - case (true, false): - return "clientStreaming" - case (false, true): - return "serverStreaming" - case (false, false): - return "unary" - } - } - - private func makeClientMethod( - for method: CodeGenerationRequest.ServiceDescriptor.MethodDescriptor, - in service: CodeGenerationRequest.ServiceDescriptor, - from codeGenerationRequest: CodeGenerationRequest - ) -> Declaration { - let parameters = self.makeParameters( - for: method, - in: service, - from: codeGenerationRequest, - includeSerializationParameters: true, - includeDefaultCallOptions: true, - includeDefaultResponseHandler: !method.isOutputStreaming - ) - let grpcMethodName = self.clientMethod( - isInputStreaming: method.isInputStreaming, - isOutputStreaming: method.isOutputStreaming - ) - let functionCall = Expression.functionCall( - calledExpression: .memberAccess( - MemberAccessDescription(left: .identifierPattern("self.client"), right: "\(grpcMethodName)") - ), - arguments: [ - .init(label: "request", expression: .identifierPattern("request")), - .init( - label: "descriptor", - expression: .identifierPattern( - "\(service.namespacedGeneratedName).Method.\(method.name.generatedUpperCase).descriptor" - ) - ), - .init(label: "serializer", expression: .identifierPattern("serializer")), - .init(label: "deserializer", expression: .identifierPattern("deserializer")), - .init(label: "options", expression: .identifierPattern("options")), - .init(label: "handler", expression: .identifierPattern("body")), - ] - ) - let body = UnaryKeywordDescription( - kind: .try, - expression: .unaryKeyword(kind: .await, expression: functionCall) - ) - - return .function( - accessModifier: self.accessModifier, - kind: .function( - name: "\(method.name.generatedLowerCase)", - isStatic: false - ), - generics: [.member("R")], - parameters: parameters, - keywords: [.async, .throws], - returnType: .identifierType(.member("R")), - whereClause: WhereClause(requirements: [.conformance("R", "Sendable")]), - body: [.expression(.unaryKeyword(body))] - ) - } - - fileprivate enum InputOutputType { - case input - case output - } -} diff --git a/Sources/GRPCCodeGen/Internal/Translator/IDLToStructuredSwiftTranslator.swift b/Sources/GRPCCodeGen/Internal/Translator/IDLToStructuredSwiftTranslator.swift deleted file mode 100644 index e0899291f..000000000 --- a/Sources/GRPCCodeGen/Internal/Translator/IDLToStructuredSwiftTranslator.swift +++ /dev/null @@ -1,259 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -/// Creates a representation for the server and client code, as well as for the enums containing useful type aliases and properties. -/// The representation is generated based on the ``CodeGenerationRequest`` object and user specifications, -/// using types from ``StructuredSwiftRepresentation``. -struct IDLToStructuredSwiftTranslator: Translator { - func translate( - codeGenerationRequest: CodeGenerationRequest, - accessLevel: SourceGenerator.Config.AccessLevel, - accessLevelOnImports: Bool, - client: Bool, - server: Bool - ) throws -> StructuredSwiftRepresentation { - try self.validateInput(codeGenerationRequest) - - var codeBlocks = [CodeBlock]() - - let typealiasTranslator = TypealiasTranslator( - client: client, - server: server, - accessLevel: accessLevel - ) - codeBlocks.append(contentsOf: try typealiasTranslator.translate(from: codeGenerationRequest)) - - if server { - let serverCodeTranslator = ServerCodeTranslator(accessLevel: accessLevel) - codeBlocks.append(contentsOf: try serverCodeTranslator.translate(from: codeGenerationRequest)) - } - - if client { - let clientCodeTranslator = ClientCodeTranslator(accessLevel: accessLevel) - codeBlocks.append(contentsOf: try clientCodeTranslator.translate(from: codeGenerationRequest)) - } - - let fileDescription = FileDescription( - topComment: .preFormatted(codeGenerationRequest.leadingTrivia), - imports: try self.makeImports( - dependencies: codeGenerationRequest.dependencies, - accessLevel: accessLevel, - accessLevelOnImports: accessLevelOnImports - ), - codeBlocks: codeBlocks - ) - - let fileName = String(codeGenerationRequest.fileName.split(separator: ".")[0]) - let file = NamedFileDescription(name: fileName, contents: fileDescription) - return StructuredSwiftRepresentation(file: file) - } - - private func makeImports( - dependencies: [CodeGenerationRequest.Dependency], - accessLevel: SourceGenerator.Config.AccessLevel, - accessLevelOnImports: Bool - ) throws -> [ImportDescription] { - var imports: [ImportDescription] = [] - imports.append( - ImportDescription( - accessLevel: accessLevelOnImports ? AccessModifier(accessLevel) : nil, - moduleName: "GRPCCore" - ) - ) - - for dependency in dependencies { - let importDescription = try self.translateImport( - dependency: dependency, - accessLevelOnImports: accessLevelOnImports - ) - imports.append(importDescription) - } - - return imports - } -} - -extension AccessModifier { - fileprivate init(_ accessLevel: SourceGenerator.Config.AccessLevel) { - switch accessLevel.level { - case .internal: self = .internal - case .package: self = .package - case .public: self = .public - } - } -} - -extension IDLToStructuredSwiftTranslator { - private func translateImport( - dependency: CodeGenerationRequest.Dependency, - accessLevelOnImports: Bool - ) throws -> ImportDescription { - var importDescription = ImportDescription( - accessLevel: accessLevelOnImports ? AccessModifier(dependency.accessLevel) : nil, - moduleName: dependency.module - ) - if let item = dependency.item { - if let matchedKind = ImportDescription.Kind(rawValue: item.kind.value.rawValue) { - importDescription.item = ImportDescription.Item(kind: matchedKind, name: item.name) - } else { - throw CodeGenError( - code: .invalidKind, - message: "Invalid kind name for import: \(item.kind.value.rawValue)" - ) - } - } - if let spi = dependency.spi { - importDescription.spi = spi - } - - switch dependency.preconcurrency.value { - case .required: - importDescription.preconcurrency = .always - case .notRequired: - importDescription.preconcurrency = .never - case .requiredOnOS(let OSs): - importDescription.preconcurrency = .onOS(OSs) - } - return importDescription - } - - private func validateInput(_ codeGenerationRequest: CodeGenerationRequest) throws { - try self.checkServiceDescriptorsAreUnique(codeGenerationRequest.services) - - let servicesByGeneratedEnumName = Dictionary( - grouping: codeGenerationRequest.services, - by: { $0.namespacedGeneratedName } - ) - try self.checkServiceEnumNamesAreUnique(for: servicesByGeneratedEnumName) - - for service in codeGenerationRequest.services { - try self.checkMethodNamesAreUnique(in: service) - } - } - - // Verify service enum names are unique. - private func checkServiceEnumNamesAreUnique( - for servicesByGeneratedEnumName: [String: [CodeGenerationRequest.ServiceDescriptor]] - ) throws { - for (generatedEnumName, services) in servicesByGeneratedEnumName { - if services.count > 1 { - throw CodeGenError( - code: .nonUniqueServiceName, - message: """ - There must be a unique (namespace, service_name) pair for each service. \ - \(generatedEnumName) is used as a _ construction for multiple services. - """ - ) - } - } - } - - // Verify method names are unique within a service. - private func checkMethodNamesAreUnique( - in service: CodeGenerationRequest.ServiceDescriptor - ) throws { - // Check that the method descriptors are unique, by checking that the base names - // of the methods of a specific service are unique. - let baseNames = service.methods.map { $0.name.base } - if let duplicatedBase = baseNames.getFirstDuplicate() { - throw CodeGenError( - code: .nonUniqueMethodName, - message: """ - Methods of a service must have unique base names. \ - \(duplicatedBase) is used as a base name for multiple methods of the \(service.name.base) service. - """ - ) - } - - // Check that generated upper case names for methods are unique within a service, to ensure that - // the enums containing type aliases for each method of a service. - let upperCaseNames = service.methods.map { $0.name.generatedUpperCase } - if let duplicatedGeneratedUpperCase = upperCaseNames.getFirstDuplicate() { - throw CodeGenError( - code: .nonUniqueMethodName, - message: """ - Methods of a service must have unique generated upper case names. \ - \(duplicatedGeneratedUpperCase) is used as a generated upper case name for multiple methods of the \(service.name.base) service. - """ - ) - } - - // Check that generated lower case names for methods are unique within a service, to ensure that - // the function declarations and definitions from the same protocols and extensions have unique names. - let lowerCaseNames = service.methods.map { $0.name.generatedLowerCase } - if let duplicatedLowerCase = lowerCaseNames.getFirstDuplicate() { - throw CodeGenError( - code: .nonUniqueMethodName, - message: """ - Methods of a service must have unique lower case names. \ - \(duplicatedLowerCase) is used as a signature name for multiple methods of the \(service.name.base) service. - """ - ) - } - } - - private func checkServiceDescriptorsAreUnique( - _ services: [CodeGenerationRequest.ServiceDescriptor] - ) throws { - var descriptors: Set = [] - for service in services { - let name = - service.namespace.base.isEmpty - ? service.name.base : "\(service.namespace.base).\(service.name.base)" - let (inserted, _) = descriptors.insert(name) - if !inserted { - throw CodeGenError( - code: .nonUniqueServiceName, - message: """ - Services must have unique descriptors. \ - \(name) is the descriptor of at least two different services. - """ - ) - } - } - } -} - -extension CodeGenerationRequest.ServiceDescriptor { - var namespacedGeneratedName: String { - if self.namespace.generatedUpperCase.isEmpty { - return self.name.generatedUpperCase - } else { - return "\(self.namespace.generatedUpperCase)_\(self.name.generatedUpperCase)" - } - } - - var fullyQualifiedName: String { - if self.namespace.base.isEmpty { - return self.name.base - } else { - return "\(self.namespace.base).\(self.name.base)" - } - } -} - -extension [String] { - internal func getFirstDuplicate() -> String? { - var seen = Set() - for element in self { - if seen.contains(element) { - return element - } - seen.insert(element) - } - return nil - } -} diff --git a/Sources/GRPCCodeGen/Internal/Translator/ServerCodeTranslator.swift b/Sources/GRPCCodeGen/Internal/Translator/ServerCodeTranslator.swift deleted file mode 100644 index c264eea30..000000000 --- a/Sources/GRPCCodeGen/Internal/Translator/ServerCodeTranslator.swift +++ /dev/null @@ -1,488 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -/// Creates a representation for the server code that will be generated based on the ``CodeGenerationRequest`` object -/// specifications, using types from ``StructuredSwiftRepresentation``. -/// -/// For example, in the case of a service called "Bar", in the "foo" namespace which has -/// one method "baz" with input type "Input" and output type "Output", the ``ServerCodeTranslator`` will create -/// a representation for the following generated code: -/// -/// ```swift -/// @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -/// public protocol Foo_BarStreamingServiceProtocol: GRPCCore.RegistrableRPCService { -/// func baz( -/// request: GRPCCore.ServerRequest.Stream -/// ) async throws -> GRPCCore.ServerResponse.Stream -/// } -/// // Conformance to `GRPCCore.RegistrableRPCService`. -/// @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -/// extension Foo_Bar.StreamingServiceProtocol { -/// public func registerMethods(with router: inout GRPCCore.RPCRouter) { -/// router.registerHandler( -/// forMethod: Foo_Bar.Method.baz.descriptor, -/// deserializer: GRPCProtobuf.ProtobufDeserializer(), -/// serializer: GRPCProtobuf.ProtobufSerializer(), -/// handler: { request in try await self.baz(request: request) } -/// ) -/// } -/// } -/// @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -/// public protocol Foo_BarServiceProtocol: Foo_Bar.StreamingServiceProtocol { -/// func baz( -/// request: GRPCCore.ServerRequest.Single -/// ) async throws -> GRPCCore.ServerResponse.Single -/// } -/// // Partial conformance to `Foo_BarStreamingServiceProtocol`. -/// @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -/// extension Foo_Bar.ServiceProtocol { -/// public func baz( -/// request: GRPCCore.ServerRequest.Stream -/// ) async throws -> GRPCCore.ServerResponse.Stream { -/// let response = try await self.baz(request: GRPCCore.ServerRequest.Single(stream: request)) -/// return GRPCCore.ServerResponse.Stream(single: response) -/// } -/// } -///``` -struct ServerCodeTranslator: SpecializedTranslator { - var accessLevel: SourceGenerator.Config.AccessLevel - - init(accessLevel: SourceGenerator.Config.AccessLevel) { - self.accessLevel = accessLevel - } - - func translate(from codeGenerationRequest: CodeGenerationRequest) throws -> [CodeBlock] { - var codeBlocks = [CodeBlock]() - for service in codeGenerationRequest.services { - // Create the streaming protocol that declares the service methods as bidirectional streaming. - let streamingProtocol = CodeBlockItem.declaration(self.makeStreamingProtocol(for: service)) - codeBlocks.append(CodeBlock(item: streamingProtocol)) - - // Create extension for implementing the 'registerRPCs' function which is a 'RegistrableRPCService' requirement. - let conformanceToRPCServiceExtension = CodeBlockItem.declaration( - self.makeConformanceToRPCServiceExtension(for: service, in: codeGenerationRequest) - ) - codeBlocks.append( - CodeBlock( - comment: .doc("Conformance to `GRPCCore.RegistrableRPCService`."), - item: conformanceToRPCServiceExtension - ) - ) - - // Create the service protocol that declares the service methods as they are described in the Source IDL (unary, - // client/server streaming or bidirectional streaming). - let serviceProtocol = CodeBlockItem.declaration(self.makeServiceProtocol(for: service)) - codeBlocks.append(CodeBlock(item: serviceProtocol)) - - // Create extension for partial conformance to the streaming protocol. - let extensionServiceProtocol = CodeBlockItem.declaration( - self.makeExtensionServiceProtocol(for: service) - ) - codeBlocks.append( - CodeBlock( - comment: .doc( - "Partial conformance to `\(self.protocolName(service: service, streaming: true))`." - ), - item: extensionServiceProtocol - ) - ) - } - - return codeBlocks - } -} - -extension ServerCodeTranslator { - private func makeStreamingProtocol( - for service: CodeGenerationRequest.ServiceDescriptor - ) -> Declaration { - let methods = service.methods.compactMap { - Declaration.commentable( - .preFormatted($0.documentation), - .function( - FunctionDescription( - signature: self.makeStreamingMethodSignature(for: $0, in: service) - ) - ) - ) - } - - let streamingProtocol = Declaration.protocol( - .init( - accessModifier: self.accessModifier, - name: self.protocolName(service: service, streaming: true), - conformances: ["GRPCCore.RegistrableRPCService"], - members: methods - ) - ) - - return .commentable( - .preFormatted(service.documentation), - .guarded(self.availabilityGuard, streamingProtocol) - ) - } - - private func makeStreamingMethodSignature( - for method: CodeGenerationRequest.ServiceDescriptor.MethodDescriptor, - in service: CodeGenerationRequest.ServiceDescriptor, - accessModifier: AccessModifier? = nil - ) -> FunctionSignatureDescription { - return FunctionSignatureDescription( - accessModifier: accessModifier, - kind: .function(name: method.name.generatedLowerCase), - parameters: [ - .init( - label: "request", - type: .generic( - wrapper: .member(["GRPCCore", "ServerRequest", "Stream"]), - wrapped: .member(method.inputType) - ) - ), - .init(label: "context", type: .member(["GRPCCore", "ServerContext"])), - ], - keywords: [.async, .throws], - returnType: .identifierType( - .generic( - wrapper: .member(["GRPCCore", "ServerResponse", "Stream"]), - wrapped: .member(method.outputType) - ) - ) - ) - } - - private func makeConformanceToRPCServiceExtension( - for service: CodeGenerationRequest.ServiceDescriptor, - in codeGenerationRequest: CodeGenerationRequest - ) -> Declaration { - let streamingProtocol = self.protocolNameTypealias(service: service, streaming: true) - let registerRPCMethod = self.makeRegisterRPCsMethod(for: service, in: codeGenerationRequest) - return .guarded( - self.availabilityGuard, - .extension( - onType: streamingProtocol, - declarations: [registerRPCMethod] - ) - ) - } - - private func makeRegisterRPCsMethod( - for service: CodeGenerationRequest.ServiceDescriptor, - in codeGenerationRequest: CodeGenerationRequest - ) -> Declaration { - let registerRPCsSignature = FunctionSignatureDescription( - accessModifier: self.accessModifier, - kind: .function(name: "registerMethods"), - parameters: [ - .init( - label: "with", - name: "router", - type: .member(["GRPCCore", "RPCRouter"]), - `inout`: true - ) - ] - ) - let registerRPCsBody = self.makeRegisterRPCsMethodBody(for: service, in: codeGenerationRequest) - return .guarded( - self.availabilityGuard, - .function(signature: registerRPCsSignature, body: registerRPCsBody) - ) - } - - private func makeRegisterRPCsMethodBody( - for service: CodeGenerationRequest.ServiceDescriptor, - in codeGenerationRequest: CodeGenerationRequest - ) -> [CodeBlock] { - let registerHandlerCalls = service.methods.compactMap { - CodeBlock.expression( - Expression.functionCall( - calledExpression: .memberAccess( - MemberAccessDescription(left: .identifierPattern("router"), right: "registerHandler") - ), - arguments: self.makeArgumentsForRegisterHandler( - for: $0, - in: service, - from: codeGenerationRequest - ) - ) - ) - } - - return registerHandlerCalls - } - - private func makeArgumentsForRegisterHandler( - for method: CodeGenerationRequest.ServiceDescriptor.MethodDescriptor, - in service: CodeGenerationRequest.ServiceDescriptor, - from codeGenerationRequest: CodeGenerationRequest - ) -> [FunctionArgumentDescription] { - var arguments = [FunctionArgumentDescription]() - arguments.append( - FunctionArgumentDescription( - label: "forMethod", - expression: .identifierPattern(self.methodDescriptorPath(for: method, service: service)) - ) - ) - - arguments.append( - FunctionArgumentDescription( - label: "deserializer", - expression: .identifierPattern(codeGenerationRequest.lookupDeserializer(method.inputType)) - ) - ) - - arguments.append( - FunctionArgumentDescription( - label: "serializer", - expression: .identifierPattern(codeGenerationRequest.lookupSerializer(method.outputType)) - ) - ) - - let rpcFunctionCall = Expression.functionCall( - calledExpression: .memberAccess( - MemberAccessDescription( - left: .identifierPattern("self"), - right: method.name.generatedLowerCase - ) - ), - arguments: [ - FunctionArgumentDescription(label: "request", expression: .identifierPattern("request")), - FunctionArgumentDescription(label: "context", expression: .identifierPattern("context")), - ] - ) - - let handlerClosureBody = Expression.unaryKeyword( - kind: .try, - expression: .unaryKeyword(kind: .await, expression: rpcFunctionCall) - ) - - arguments.append( - FunctionArgumentDescription( - label: "handler", - expression: .closureInvocation( - ClosureInvocationDescription( - argumentNames: ["request", "context"], - body: [.expression(handlerClosureBody)] - ) - ) - ) - ) - - return arguments - } - - private func makeServiceProtocol( - for service: CodeGenerationRequest.ServiceDescriptor - ) -> Declaration { - let methods = service.methods.compactMap { - self.makeServiceProtocolMethod(for: $0, in: service) - } - let protocolName = self.protocolName(service: service, streaming: false) - let streamingProtocol = self.protocolNameTypealias(service: service, streaming: true) - - return .commentable( - .preFormatted(service.documentation), - .guarded( - self.availabilityGuard, - .protocol( - ProtocolDescription( - accessModifier: self.accessModifier, - name: protocolName, - conformances: [streamingProtocol], - members: methods - ) - ) - ) - ) - } - - private func makeServiceProtocolMethod( - for method: CodeGenerationRequest.ServiceDescriptor.MethodDescriptor, - in service: CodeGenerationRequest.ServiceDescriptor, - accessModifier: AccessModifier? = nil - ) -> Declaration { - let inputStreaming = method.isInputStreaming ? "Stream" : "Single" - let outputStreaming = method.isOutputStreaming ? "Stream" : "Single" - - let functionSignature = FunctionSignatureDescription( - accessModifier: accessModifier, - kind: .function(name: method.name.generatedLowerCase), - parameters: [ - .init( - label: "request", - type: - .generic( - wrapper: .member(["GRPCCore", "ServerRequest", inputStreaming]), - wrapped: .member(method.inputType) - ) - ), - .init(label: "context", type: .member(["GRPCCore", "ServerContext"])), - ], - keywords: [.async, .throws], - returnType: .identifierType( - .generic( - wrapper: .member(["GRPCCore", "ServerResponse", outputStreaming]), - wrapped: .member(method.outputType) - ) - ) - ) - - return .commentable( - .preFormatted(method.documentation), - .function(FunctionDescription(signature: functionSignature)) - ) - } - - private func makeExtensionServiceProtocol( - for service: CodeGenerationRequest.ServiceDescriptor - ) -> Declaration { - let methods = service.methods.compactMap { - self.makeServiceProtocolExtensionMethod(for: $0, in: service) - } - - let protocolName = self.protocolNameTypealias(service: service, streaming: false) - return .guarded( - self.availabilityGuard, - .extension( - onType: protocolName, - declarations: methods - ) - ) - } - - private func makeServiceProtocolExtensionMethod( - for method: CodeGenerationRequest.ServiceDescriptor.MethodDescriptor, - in service: CodeGenerationRequest.ServiceDescriptor - ) -> Declaration? { - // The method has the same definition in StreamingServiceProtocol and ServiceProtocol. - if method.isInputStreaming && method.isOutputStreaming { - return nil - } - - let response = CodeBlock(item: .declaration(self.makeResponse(for: method))) - let returnStatement = CodeBlock(item: .expression(self.makeReturnStatement(for: method))) - - return .function( - signature: self.makeStreamingMethodSignature( - for: method, - in: service, - accessModifier: self.accessModifier - ), - body: [response, returnStatement] - ) - } - - private func makeResponse( - for method: CodeGenerationRequest.ServiceDescriptor.MethodDescriptor - ) -> Declaration { - let serverRequest: Expression - if !method.isInputStreaming { - // Transform the streaming request into a unary request. - serverRequest = Expression.functionCall( - calledExpression: .memberAccess( - MemberAccessDescription( - left: .identifierPattern("GRPCCore.ServerRequest"), - right: "Single" - ) - ), - arguments: [ - FunctionArgumentDescription(label: "stream", expression: .identifierPattern("request")) - ] - ) - } else { - serverRequest = Expression.identifierPattern("request") - } - // Call to the corresponding ServiceProtocol method. - let serviceProtocolMethod = Expression.functionCall( - calledExpression: .memberAccess( - MemberAccessDescription( - left: .identifierPattern("self"), - right: method.name.generatedLowerCase - ) - ), - arguments: [ - FunctionArgumentDescription(label: "request", expression: serverRequest), - FunctionArgumentDescription(label: "context", expression: .identifier(.pattern("context"))), - ] - ) - - let responseValue = Expression.unaryKeyword( - kind: .try, - expression: .unaryKeyword(kind: .await, expression: serviceProtocolMethod) - ) - - return .variable(kind: .let, left: "response", right: responseValue) - } - - private func makeReturnStatement( - for method: CodeGenerationRequest.ServiceDescriptor.MethodDescriptor - ) -> Expression { - let returnValue: Expression - // Transforming the unary response into a streaming one. - if !method.isOutputStreaming { - returnValue = .functionCall( - calledExpression: .memberAccess( - MemberAccessDescription( - left: .identifierType(.member(["GRPCCore", "ServerResponse"])), - right: "Stream" - ) - ), - arguments: [ - (FunctionArgumentDescription(label: "single", expression: .identifierPattern("response"))) - ] - ) - } else { - returnValue = .identifierPattern("response") - } - - return .unaryKeyword(kind: .return, expression: returnValue) - } - - fileprivate enum InputOutputType { - case input - case output - } - - /// Generates the fully qualified name of a method descriptor. - private func methodDescriptorPath( - for method: CodeGenerationRequest.ServiceDescriptor.MethodDescriptor, - service: CodeGenerationRequest.ServiceDescriptor - ) -> String { - return - "\(service.namespacedGeneratedName).Method.\(method.name.generatedUpperCase).descriptor" - } - - /// Generates the fully qualified name of the type alias for a service protocol. - internal func protocolNameTypealias( - service: CodeGenerationRequest.ServiceDescriptor, - streaming: Bool - ) -> String { - if streaming { - return "\(service.namespacedGeneratedName).StreamingServiceProtocol" - } - return "\(service.namespacedGeneratedName).ServiceProtocol" - } - - /// Generates the name of a service protocol. - internal func protocolName( - service: CodeGenerationRequest.ServiceDescriptor, - streaming: Bool - ) -> String { - if streaming { - return "\(service.namespacedGeneratedName)StreamingServiceProtocol" - } - return "\(service.namespacedGeneratedName)ServiceProtocol" - } -} diff --git a/Sources/GRPCCodeGen/Internal/Translator/SpecializedTranslator.swift b/Sources/GRPCCodeGen/Internal/Translator/SpecializedTranslator.swift deleted file mode 100644 index 1db3fce02..000000000 --- a/Sources/GRPCCodeGen/Internal/Translator/SpecializedTranslator.swift +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -/// Represents one responsibility of the ``Translator``: either the type aliases translation, -/// the server code translation or the client code translation. -protocol SpecializedTranslator { - - /// The ``SourceGenerator.Config.AccessLevel`` object used to represent the visibility level used in the generated code. - var accessLevel: SourceGenerator.Config.AccessLevel { get } - - /// Generates an array of ``CodeBlock`` elements that will be part of the ``StructuredSwiftRepresentation`` object - /// created by the ``Translator``. - /// - /// - Parameters: - /// - codeGenerationRequest: The ``CodeGenerationRequest`` object used to represent a Source IDL description of RPCs. - /// - Returns: An array of ``CodeBlock`` elements. - /// - /// - SeeAlso: ``CodeGenerationRequest``, ``Translator``, ``CodeBlock``. - func translate(from codeGenerationRequest: CodeGenerationRequest) throws -> [CodeBlock] -} - -extension SpecializedTranslator { - /// The access modifier that corresponds with the access level from ``SourceGenerator.Configuration``. - internal var accessModifier: AccessModifier { - get { - switch accessLevel.level { - case .internal: - return AccessModifier.internal - case .package: - return AccessModifier.package - case .public: - return AccessModifier.public - } - } - } - - internal var availabilityGuard: AvailabilityDescription { - AvailabilityDescription(osVersions: [ - .init(os: .macOS, version: "15.0"), - .init(os: .iOS, version: "18.0"), - .init(os: .watchOS, version: "11.0"), - .init(os: .tvOS, version: "18.0"), - .init(os: .visionOS, version: "2.0"), - ]) - } -} diff --git a/Sources/GRPCCodeGen/Internal/Translator/Translator.swift b/Sources/GRPCCodeGen/Internal/Translator/Translator.swift deleted file mode 100644 index 36b1c665f..000000000 --- a/Sources/GRPCCodeGen/Internal/Translator/Translator.swift +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -/// Transforms ``CodeGenerationRequest`` objects into ``StructuredSwiftRepresentation`` objects. -/// -/// It represents the first step of the code generation process for IDL described RPCs. -protocol Translator { - /// Translates the provided ``CodeGenerationRequest`` object, into Swift code representation. - /// - Parameters: - /// - codeGenerationRequest: The IDL described RPCs representation. - /// - accessLevel: The access level that will restrict the protocols, extensions and methods in the generated code. - /// - accessLevelOnImports: Whether access levels should be included on imports. - /// - client: Whether or not client code should be generated from the IDL described RPCs representation. - /// - server: Whether or not server code should be generated from the IDL described RPCs representation. - /// - Returns: A structured Swift representation of the generated code. - /// - Throws: An error if there are issues translating the codeGenerationRequest. - func translate( - codeGenerationRequest: CodeGenerationRequest, - accessLevel: SourceGenerator.Config.AccessLevel, - accessLevelOnImports: Bool, - client: Bool, - server: Bool - ) throws -> StructuredSwiftRepresentation -} diff --git a/Sources/GRPCCodeGen/Internal/Translator/TypealiasTranslator.swift b/Sources/GRPCCodeGen/Internal/Translator/TypealiasTranslator.swift deleted file mode 100644 index a6bcc55ae..000000000 --- a/Sources/GRPCCodeGen/Internal/Translator/TypealiasTranslator.swift +++ /dev/null @@ -1,374 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -/// Creates enums containing useful type aliases and static properties for the methods, services and -/// namespaces described in a ``CodeGenerationRequest`` object, using types from -/// ``StructuredSwiftRepresentation``. -/// -/// For example, in the case of the ``Echo`` service, the ``TypealiasTranslator`` will create -/// a representation for the following generated code: -/// ```swift -/// public enum Echo_Echo { -/// public static let descriptor = GRPCCore.ServiceDescriptor.echo_Echo -/// -/// public enum Method { -/// public enum Get { -/// public typealias Input = Echo_EchoRequest -/// public typealias Output = Echo_EchoResponse -/// public static let descriptor = GRPCCore.MethodDescriptor( -/// service: Echo_Echo.descriptor.fullyQualifiedService, -/// method: "Get" -/// ) -/// } -/// -/// public enum Collect { -/// public typealias Input = Echo_EchoRequest -/// public typealias Output = Echo_EchoResponse -/// public static let descriptor = GRPCCore.MethodDescriptor( -/// service: Echo_Echo.descriptor.fullyQualifiedService, -/// method: "Collect" -/// ) -/// } -/// // ... -/// -/// public static let descriptors: [GRPCCore.MethodDescriptor] = [ -/// Get.descriptor, -/// Collect.descriptor, -/// // ... -/// ] -/// } -/// -/// @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -/// public typealias StreamingServiceProtocol = Echo_EchoServiceStreamingProtocol -/// @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -/// public typealias ServiceProtocol = Echo_EchoServiceProtocol -/// -/// } -/// -/// extension GRPCCore.ServiceDescriptor { -/// public static let echo_Echo = Self( -/// package: "echo", -/// service: "Echo" -/// ) -/// } -/// ``` -/// -/// A ``CodeGenerationRequest`` can contain multiple namespaces, so the TypealiasTranslator will create a ``CodeBlock`` -/// for each namespace. -struct TypealiasTranslator: SpecializedTranslator { - let client: Bool - let server: Bool - let accessLevel: SourceGenerator.Config.AccessLevel - - init(client: Bool, server: Bool, accessLevel: SourceGenerator.Config.AccessLevel) { - self.client = client - self.server = server - self.accessLevel = accessLevel - } - - func translate(from codeGenerationRequest: CodeGenerationRequest) throws -> [CodeBlock] { - var codeBlocks = [CodeBlock]() - let services = codeGenerationRequest.services - let servicesByEnumName = Dictionary( - grouping: services, - by: { $0.namespacedGeneratedName } - ) - - // Sorting the keys of the dictionary is necessary so that the generated enums are deterministically ordered. - for (generatedEnumName, services) in servicesByEnumName.sorted(by: { $0.key < $1.key }) { - for service in services { - codeBlocks.append( - CodeBlock( - item: .declaration(try self.makeServiceEnum(from: service, named: generatedEnumName)) - ) - ) - - codeBlocks.append( - CodeBlock(item: .declaration(self.makeServiceDescriptorExtension(for: service))) - ) - } - } - - return codeBlocks - } -} - -extension TypealiasTranslator { - private func makeServiceEnum( - from service: CodeGenerationRequest.ServiceDescriptor, - named name: String - ) throws -> Declaration { - var serviceEnum = EnumDescription( - accessModifier: self.accessModifier, - name: name - ) - var methodsEnum = EnumDescription(accessModifier: self.accessModifier, name: "Method") - let methods = service.methods - - // Create the method specific enums. - for method in methods { - let methodEnum = self.makeMethodEnum(from: method, in: service) - methodsEnum.members.append(methodEnum) - } - - // Create the method descriptor array. - let methodDescriptorsDeclaration = self.makeMethodDescriptors(for: service) - methodsEnum.members.append(methodDescriptorsDeclaration) - - // Create the static service descriptor property. - let staticServiceDescriptorProperty = self.makeStaticServiceDescriptorProperty(for: service) - - serviceEnum.members.append(.variable(staticServiceDescriptorProperty)) - serviceEnum.members.append(.enum(methodsEnum)) - - if self.server { - // Create the streaming and non-streaming service protocol type aliases. - let serviceProtocols = self.makeServiceProtocolsTypealiases(for: service) - serviceEnum.members.append(contentsOf: serviceProtocols) - } - - if self.client { - // Create the client protocol type alias. - let clientProtocol = self.makeClientProtocolTypealias(for: service) - serviceEnum.members.append(clientProtocol) - - // Create type alias for Client struct. - let clientStruct = self.makeClientStructTypealias(for: service) - serviceEnum.members.append(clientStruct) - } - - return .enum(serviceEnum) - } - - private func makeMethodEnum( - from method: CodeGenerationRequest.ServiceDescriptor.MethodDescriptor, - in service: CodeGenerationRequest.ServiceDescriptor - ) -> Declaration { - var methodEnum = EnumDescription(name: method.name.generatedUpperCase) - - let inputTypealias = Declaration.typealias( - accessModifier: self.accessModifier, - name: "Input", - existingType: .member([method.inputType]) - ) - let outputTypealias = Declaration.typealias( - accessModifier: self.accessModifier, - name: "Output", - existingType: .member([method.outputType]) - ) - let descriptorVariable = self.makeMethodDescriptor( - from: method, - in: service - ) - methodEnum.members.append(inputTypealias) - methodEnum.members.append(outputTypealias) - methodEnum.members.append(descriptorVariable) - - methodEnum.accessModifier = self.accessModifier - - return .enum(methodEnum) - } - - private func makeMethodDescriptor( - from method: CodeGenerationRequest.ServiceDescriptor.MethodDescriptor, - in service: CodeGenerationRequest.ServiceDescriptor - ) -> Declaration { - let fullyQualifiedService = MemberAccessDescription( - left: .memberAccess( - MemberAccessDescription( - left: .identifierType(.member([service.namespacedGeneratedName])), - right: "descriptor" - ) - ), - right: "fullyQualifiedService" - ) - - let descriptorDeclarationLeft = Expression.identifier(.pattern("descriptor")) - let descriptorDeclarationRight = Expression.functionCall( - FunctionCallDescription( - calledExpression: .identifierType(.member(["GRPCCore", "MethodDescriptor"])), - arguments: [ - FunctionArgumentDescription( - label: "service", - expression: .memberAccess(fullyQualifiedService) - ), - FunctionArgumentDescription( - label: "method", - expression: .literal(method.name.base) - ), - ] - ) - ) - - return .variable( - accessModifier: self.accessModifier, - isStatic: true, - kind: .let, - left: descriptorDeclarationLeft, - right: descriptorDeclarationRight - ) - } - - private func makeMethodDescriptors( - for service: CodeGenerationRequest.ServiceDescriptor - ) -> Declaration { - var methodDescriptors = [Expression]() - let methodNames = service.methods.map { $0.name.generatedUpperCase } - - for methodName in methodNames { - let methodDescriptorPath = Expression.memberAccess( - MemberAccessDescription( - left: .identifierType( - .member([methodName]) - ), - right: "descriptor" - ) - ) - methodDescriptors.append(methodDescriptorPath) - } - - return .variable( - accessModifier: self.accessModifier, - isStatic: true, - kind: .let, - left: .identifier(.pattern("descriptors")), - type: .array(.member(["GRPCCore", "MethodDescriptor"])), - right: .literal(.array(methodDescriptors)) - ) - } - - private func makeServiceProtocolsTypealiases( - for service: CodeGenerationRequest.ServiceDescriptor - ) -> [Declaration] { - let streamingServiceProtocolTypealias = Declaration.typealias( - accessModifier: self.accessModifier, - name: "StreamingServiceProtocol", - existingType: .member("\(service.namespacedGeneratedName)StreamingServiceProtocol") - ) - let serviceProtocolTypealias = Declaration.typealias( - accessModifier: self.accessModifier, - name: "ServiceProtocol", - existingType: .member("\(service.namespacedGeneratedName)ServiceProtocol") - ) - - return [ - .guarded( - self.availabilityGuard, - streamingServiceProtocolTypealias - ), - .guarded( - self.availabilityGuard, - serviceProtocolTypealias - ), - ] - } - - private func makeClientProtocolTypealias( - for service: CodeGenerationRequest.ServiceDescriptor - ) -> Declaration { - return .guarded( - self.availabilityGuard, - .typealias( - accessModifier: self.accessModifier, - name: "ClientProtocol", - existingType: .member("\(service.namespacedGeneratedName)ClientProtocol") - ) - ) - } - - private func makeClientStructTypealias( - for service: CodeGenerationRequest.ServiceDescriptor - ) -> Declaration { - return .guarded( - self.availabilityGuard, - .typealias( - accessModifier: self.accessModifier, - name: "Client", - existingType: .member("\(service.namespacedGeneratedName)Client") - ) - ) - } - - private func makeServiceIdentifier(_ service: CodeGenerationRequest.ServiceDescriptor) -> String { - let prefix: String - - if service.namespace.normalizedBase.isEmpty { - prefix = "" - } else { - prefix = service.namespace.normalizedBase + "_" - } - - return prefix + service.name.normalizedBase - } - - private func makeStaticServiceDescriptorProperty( - for service: CodeGenerationRequest.ServiceDescriptor - ) -> VariableDescription { - let serviceIdentifier = makeServiceIdentifier(service) - - return VariableDescription( - accessModifier: self.accessModifier, - isStatic: true, - kind: .let, - left: .identifierPattern("descriptor"), - right: .memberAccess( - MemberAccessDescription( - left: .identifierPattern("GRPCCore.ServiceDescriptor"), - right: serviceIdentifier - ) - ) - ) - } - - private func makeServiceDescriptorExtension( - for service: CodeGenerationRequest.ServiceDescriptor - ) -> Declaration { - let serviceIdentifier = makeServiceIdentifier(service) - - let serviceDescriptorInitialization = Expression.functionCall( - FunctionCallDescription( - calledExpression: .identifierType(.member("Self")), - arguments: [ - FunctionArgumentDescription( - label: "package", - expression: .literal(service.namespace.base) - ), - FunctionArgumentDescription( - label: "service", - expression: .literal(service.name.base) - ), - ] - ) - ) - - return .extension( - ExtensionDescription( - onType: "GRPCCore.ServiceDescriptor", - declarations: [ - .variable( - VariableDescription( - accessModifier: self.accessModifier, - isStatic: true, - kind: .let, - left: .identifier(.pattern(serviceIdentifier)), - right: serviceDescriptorInitialization - ) - ) - ] - ) - ) - } -} diff --git a/Sources/GRPCCodeGen/Internal/TypeName.swift b/Sources/GRPCCodeGen/Internal/TypeName.swift deleted file mode 100644 index 0152de6a0..000000000 --- a/Sources/GRPCCodeGen/Internal/TypeName.swift +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ -//===----------------------------------------------------------------------===// -// -// This source file is part of the SwiftOpenAPIGenerator open source project -// -// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// -import Foundation - -/// A fully-qualified type name that contains the components of the Swift -/// type name. -/// -/// Use the type name to define a type, see also `TypeUsage` when referring -/// to a type. -struct TypeName: Hashable { - /// A list of components that make up the type name. - internal let components: [String] - - /// Creates a new type name with the specified list of components. - /// - Parameter components: A list of components for the type. - init(components: [String]) { - precondition(!components.isEmpty, "TypeName path cannot be empty") - self.components = components - } - - /// A string representation of the fully qualified Swift type name. - /// - /// For example: `Swift.Int`. - var fullyQualifiedName: String { components.joined(separator: ".") } - - /// A string representation of the last path component of the Swift - /// type name. - /// - /// For example: `Int`. - var shortName: String { components.last! } - - /// Returns a type name by appending the specified component to the - /// current type name. - /// - /// In other words, returns a type name for a child type. - /// - Parameters: - /// - component: The name of the Swift type component. - /// - Returns: A new type name. - func appending(component: String) -> Self { - return .init(components: components + [component]) - } - - /// Returns a type name by removing the last component from the current - /// type name. - /// - /// In other words, returns a type name for the parent type. - var parent: TypeName { - precondition(components.count >= 1, "Cannot get the parent of a root type") - return .init(components: components.dropLast()) - } -} - -extension TypeName: CustomStringConvertible { - var description: String { - return fullyQualifiedName - } -} - -extension TypeName { - /// Returns the type name for the String type. - static var string: Self { .swift("String") } - - /// Returns the type name for the Int type. - static var int: Self { .swift("Int") } - - /// Returns a type name for a type with the specified name in the - /// Swift module. - /// - Parameter name: The name of the type. - /// - Returns: A TypeName representing the specified type within the Swift module. - static func swift(_ name: String) -> TypeName { TypeName(components: ["Swift", name]) } - - /// Returns a type name for a type with the specified name in the - /// Foundation module. - /// - Parameter name: The name of the type. - /// - Returns: A TypeName representing the specified type within the Foundation module. - static func foundation(_ name: String) -> TypeName { - TypeName(components: ["Foundation", name]) - } -} diff --git a/Sources/GRPCCodeGen/Internal/TypeUsage.swift b/Sources/GRPCCodeGen/Internal/TypeUsage.swift deleted file mode 100644 index 2fdf0a8fa..000000000 --- a/Sources/GRPCCodeGen/Internal/TypeUsage.swift +++ /dev/null @@ -1,232 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ -//===----------------------------------------------------------------------===// -// -// This source file is part of the SwiftOpenAPIGenerator open source project -// -// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -/// A reference to a Swift type, including modifiers such as whether the -/// type is wrapped in an optional, an array, or a dictionary. -/// -/// Whenever unsure whether to use `TypeUsage` or `TypeName` in a new API, -/// consider whether you need to define a type or refer to a type. -/// -/// To define a type, use `TypeName`, and to refer to a type, use `TypeUsage`. -/// -/// This type is not meant to represent all the various ways types can be -/// wrapped in Swift, only the ways we wrap things in this project. For example, -/// double optionals (`String??`) are automatically collapsed into a single -/// optional, and so on. -struct TypeUsage { - - /// Describes either a type name or a type usage. - fileprivate indirect enum Wrapped { - - /// A type name, used to define a type. - case name(TypeName) - - /// A type usage, used to refer to a type. - case usage(TypeUsage) - } - - /// The underlying type. - fileprivate var wrapped: Wrapped - - /// Describes the usage of the wrapped type. - fileprivate enum Usage { - - /// An unchanged underlying type. - /// - /// For example: `Wrapped` stays `Wrapped`. - case identity - - /// An optional wrapper for the underlying type. - /// - /// For example: `Wrapped` becomes `Wrapped?`. - case optional - - /// An array wrapped for the underlying type. - /// - /// For example: `Wrapped` becomes `[Wrapped]`. - case array - - /// A dictionary value wrapper for the underlying type. - /// - /// For example: `Wrapped` becomes `[String: Wrapped]`. - case dictionaryValue - - /// A generic type wrapper for the underlying type. - /// - /// For example, `Wrapped` becomes `Wrapper`. - case generic(wrapper: TypeName) - } - - /// The type usage applied to the underlying type. - fileprivate var usage: Usage -} - -extension TypeUsage: CustomStringConvertible { var description: String { fullyQualifiedName } } - -extension TypeUsage { - - /// A Boolean value that indicates whether the type is optional. - var isOptional: Bool { - guard case .optional = usage else { return false } - return true - } - - /// A string representation of the last component of the Swift type name. - /// - /// For example: `Int`. - var shortName: String { - let component: String - switch wrapped { - case let .name(typeName): component = typeName.shortName - case let .usage(usage): component = usage.shortName - } - return applied(to: component) - } - - /// A string representation of the fully qualified type name. - /// - /// For example: `Swift.Int`. - var fullyQualifiedName: String { - let component: String - switch wrapped { - case let .name(typeName): component = typeName.fullyQualifiedName - case let .usage(usage): component = usage.fullyQualifiedName - } - return applied(to: component) - } - - /// A string representation of the fully qualified Swift type name, with - /// any optional wrapping removed. - /// - /// For example: `Swift.Int`. - var fullyQualifiedNonOptionalName: String { withOptional(false).fullyQualifiedName } - - /// Returns a string representation of the type usage applied to the - /// specified Swift path component. - /// - Parameter component: A Swift path component. - /// - Returns: A string representation of the specified Swift path component with the applied type usage. - private func applied(to component: String) -> String { - switch usage { - case .identity: return component - case .optional: return component + "?" - case .array: return "[" + component + "]" - case .dictionaryValue: return "[String: " + component + "]" - case .generic(wrapper: let wrapper): - return "\(wrapper.fullyQualifiedName)<" + component + ">" - } - } - - /// The type name wrapped by the current type usage. - var typeName: TypeName { - switch wrapped { - case .name(let typeName): return typeName - case .usage(let typeUsage): return typeUsage.typeName - } - } - - /// A type usage created by treating the current type usage as an optional - /// type. - var asOptional: Self { - // Don't double wrap optionals - guard !isOptional else { return self } - return TypeUsage(wrapped: .usage(self), usage: .optional) - } - - /// A type usage created by removing the outer type usage wrapper. - private var unwrappedOneLevel: Self { - switch wrapped { - case let .usage(usage): return usage - case let .name(typeName): return typeName.asUsage - } - } - - /// Returns a type usage created by adding or removing an optional wrapper, - /// controlled by the specified parameter. - /// - Parameter isOptional: If `true`, wraps the current type usage in - /// an optional. If `false`, removes a potential optional wrapper from the - /// top level. - /// - Returns: A type usage with the adjusted optionality based on the `isOptional` parameter. - func withOptional(_ isOptional: Bool) -> Self { - if (isOptional && self.isOptional) || (!isOptional && !self.isOptional) { return self } - guard isOptional else { return unwrappedOneLevel } - return asOptional - } - - /// A type usage created by treating the current type usage as the element - /// type of an array. - /// - Returns: A type usage for the array. - var asArray: Self { TypeUsage(wrapped: .usage(self), usage: .array) } - - /// A type usage created by treating the current type usage as the value - /// type of a dictionary. - /// - Returns: A type usage for the dictionary. - var asDictionaryValue: Self { TypeUsage(wrapped: .usage(self), usage: .dictionaryValue) } - - /// A type usage created by wrapping the current type usage inside the - /// wrapper type, where the wrapper type is generic over the current type. - func asWrapped(in wrapper: TypeName) -> Self { - TypeUsage(wrapped: .usage(self), usage: .generic(wrapper: wrapper)) - } -} - -extension TypeName { - - /// A type usage that wraps the current type name without changing it. - var asUsage: TypeUsage { TypeUsage(wrapped: .name(self), usage: .identity) } -} - -extension ExistingTypeDescription { - - /// Creates a new type description from the provided type usage's wrapped - /// value. - /// - Parameter wrapped: The wrapped value. - private init(_ wrapped: TypeUsage.Wrapped) { - switch wrapped { - case .name(let typeName): self = .init(typeName) - case .usage(let typeUsage): self = .init(typeUsage) - } - } - - /// Creates a new type description from the provided type name. - /// - Parameter typeName: A type name. - init(_ typeName: TypeName) { self = .member(typeName.components) } - - /// Creates a new type description from the provided type usage. - /// - Parameter typeUsage: A type usage. - init(_ typeUsage: TypeUsage) { - switch typeUsage.usage { - case .generic(wrapper: let wrapper): - self = .generic(wrapper: .init(wrapper), wrapped: .init(typeUsage.wrapped)) - case .optional: self = .optional(.init(typeUsage.wrapped)) - case .identity: self = .init(typeUsage.wrapped) - case .array: self = .array(.init(typeUsage.wrapped)) - case .dictionaryValue: self = .dictionaryValue(.init(typeUsage.wrapped)) - } - } -} diff --git a/Sources/GRPCCodeGen/SourceFile.swift b/Sources/GRPCCodeGen/SourceFile.swift deleted file mode 100644 index c435fb100..000000000 --- a/Sources/GRPCCodeGen/SourceFile.swift +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -/// Representation of the file to be created by the code generator, that contains the -/// generated Swift source code. -public struct SourceFile: Sendable, Hashable { - /// The base name of the file. - public var name: String - - /// The generated code as a String. - public var contents: String - - /// Creates a representation of a file containing Swift code with the specified name - /// and contents. - public init(name: String, contents: String) { - self.name = name - self.contents = contents - } -} diff --git a/Sources/GRPCCodeGen/SourceGenerator.swift b/Sources/GRPCCodeGen/SourceGenerator.swift deleted file mode 100644 index 454258bc6..000000000 --- a/Sources/GRPCCodeGen/SourceGenerator.swift +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -/// Creates a ``SourceFile`` containing the generated code for the RPCs represented in a ``CodeGenerationRequest`` object. -public struct SourceGenerator: Sendable { - /// The options regarding the access level, indentation for the generated code - /// and whether to generate server and client code. - public var config: Config - - public init(config: Config) { - self.config = config - } - - /// User options for the CodeGeneration. - public struct Config: Sendable { - /// The access level the generated code will have. - public var accessLevel: AccessLevel - /// Whether imports have explicit access levels. - public var accessLevelOnImports: Bool - /// The indentation of the generated code as the number of spaces. - public var indentation: Int - /// Whether or not client code should be generated. - public var client: Bool - /// Whether or not server code should be generated. - public var server: Bool - - /// Creates a new configuration. - /// - /// - Parameters: - /// - accessLevel: The access level the generated code will have. - /// - accessLevelOnImports: Whether imports have explicit access levels. - /// - client: Whether or not client code should be generated. - /// - server: Whether or not server code should be generated. - /// - indentation: The indentation of the generated code as the number of spaces. - public init( - accessLevel: AccessLevel, - accessLevelOnImports: Bool, - client: Bool, - server: Bool, - indentation: Int = 4 - ) { - self.accessLevel = accessLevel - self.accessLevelOnImports = accessLevelOnImports - self.indentation = indentation - self.client = client - self.server = server - } - - /// The possible access levels for the generated code. - public struct AccessLevel: Sendable, Hashable { - internal var level: Level - internal enum Level { - case `internal` - case `public` - case `package` - } - - /// The generated code will have `internal` access level. - public static var `internal`: Self { Self(level: .`internal`) } - - /// The generated code will have `public` access level. - public static var `public`: Self { Self(level: .`public`) } - - /// The generated code will have `package` access level. - public static var `package`: Self { Self(level: .`package`) } - } - } - - /// The function that transforms a ``CodeGenerationRequest`` object into a ``SourceFile`` object containing - /// the generated code, in accordance to the configurations set by the user for the ``SourceGenerator``. - public func generate( - _ request: CodeGenerationRequest - ) throws -> SourceFile { - let translator = IDLToStructuredSwiftTranslator() - let textRenderer = TextBasedRenderer(indentation: self.config.indentation) - - let structuredSwiftRepresentation = try translator.translate( - codeGenerationRequest: request, - accessLevel: self.config.accessLevel, - accessLevelOnImports: self.config.accessLevelOnImports, - client: self.config.client, - server: self.config.server - ) - let sourceFile = try textRenderer.render(structured: structuredSwiftRepresentation) - - return sourceFile - } -} diff --git a/Sources/GRPCCore/Call/Client/CallOptions.swift b/Sources/GRPCCore/Call/Client/CallOptions.swift deleted file mode 100644 index ce6388050..000000000 --- a/Sources/GRPCCore/Call/Client/CallOptions.swift +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -/// Options applied to a call. -/// -/// If set, these options are used in preference to any options configured on -/// the client or its transport. -/// -/// You can create the default set of options, which defers all possible -/// configuration to the transport, by using ``CallOptions/defaults``. -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -public struct CallOptions: Sendable { - /// The default timeout for the RPC. - /// - /// If no reply is received in the specified amount of time the request is aborted - /// with an ``RPCError`` with code ``RPCError/Code/deadlineExceeded``. - /// - /// The actual deadline used will be the minimum of the value specified here - /// and the value set by the application by the client API. If either one isn't set - /// then the other value is used. If neither is set then the request has no deadline. - /// - /// The timeout applies to the overall execution of an RPC. If, for example, a retry - /// policy is set then the timeout begins when the first attempt is started and _isn't_ reset - /// when subsequent attempts start. - public var timeout: Duration? - - /// Whether RPCs for this method should wait until the connection is ready. - /// - /// If `false` the RPC will abort immediately if there is a transient failure connecting to - /// the server. Otherwise gRPC will attempt to connect until the deadline is exceeded. - public var waitForReady: Bool? - - /// The maximum allowed payload size in bytes for an individual request message. - /// - /// If a client attempts to send an object larger than this value, it will not be sent and the - /// client will see an error. Note that 0 is a valid value, meaning that the request message - /// must be empty. - /// - /// Note that if compression is used the uncompressed message size is validated. - public var maxRequestMessageBytes: Int? - - /// The maximum allowed payload size in bytes for an individual response message. - /// - /// If a server attempts to send an object larger than this value, it will not - /// be sent, and an error will be sent to the client instead. Note that 0 is a valid value, - /// meaning that the response message must be empty. - /// - /// Note that if compression is used the uncompressed message size is validated. - public var maxResponseMessageBytes: Int? - - /// The policy determining how many times, and when, the RPC is executed. - /// - /// There are two policy types: - /// 1. Retry - /// 2. Hedging - /// - /// The retry policy allows an RPC to be retried a limited number of times if the RPC - /// fails with one of the configured set of status codes. RPCs are only retried if they - /// fail immediately, that is, the first response part received from the server is a - /// status code. - /// - /// The hedging policy allows an RPC to be executed multiple times concurrently. Typically - /// each execution will be staggered by some delay. The first successful response will be - /// reported to the client. Hedging is only suitable for idempotent RPCs. - public var executionPolicy: RPCExecutionPolicy? - - /// The compression used for the call. - /// - /// Compression in gRPC is asymmetrical: the server may compress response messages using a - /// different algorithm than the client used to compress request messages. This configuration - /// controls the compression used by the client for request messages. - /// - /// Note that this configuration is advisory: not all transports support compression and may - /// ignore this configuration. Transports which support compression will use this configuration - /// in preference to the algorithm configured at a transport level. If the transport hasn't - /// enabled the use of the algorithm then compression won't be used for the call. - /// - /// If `nil` the value configured on the transport will be used instead. - public var compression: CompressionAlgorithm? - - internal init( - timeout: Duration?, - waitForReady: Bool?, - maxRequestMessageBytes: Int?, - maxResponseMessageBytes: Int?, - executionPolicy: RPCExecutionPolicy?, - compression: CompressionAlgorithm? - ) { - self.timeout = timeout - self.waitForReady = waitForReady - self.maxRequestMessageBytes = maxRequestMessageBytes - self.maxResponseMessageBytes = maxResponseMessageBytes - self.executionPolicy = executionPolicy - self.compression = compression - } -} - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -extension CallOptions { - /// Default call options. - /// - /// The default values (`nil`) defer values to the underlying transport. - public static var defaults: Self { - Self( - timeout: nil, - waitForReady: nil, - maxRequestMessageBytes: nil, - maxResponseMessageBytes: nil, - executionPolicy: nil, - compression: nil - ) - } -} - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -extension CallOptions { - package mutating func formUnion(with methodConfig: MethodConfig?) { - guard let methodConfig = methodConfig else { return } - - self.timeout.setIfNone(to: methodConfig.timeout) - self.waitForReady.setIfNone(to: methodConfig.waitForReady) - self.maxRequestMessageBytes.setIfNone(to: methodConfig.maxRequestMessageBytes) - self.maxResponseMessageBytes.setIfNone(to: methodConfig.maxResponseMessageBytes) - self.executionPolicy.setIfNone(to: methodConfig.executionPolicy) - } -} - -extension Optional { - fileprivate mutating func setIfNone(to value: Self) { - switch self { - case .some: - () - case .none: - self = value - } - } -} diff --git a/Sources/GRPCCore/Call/Client/ClientContext.swift b/Sources/GRPCCore/Call/Client/ClientContext.swift deleted file mode 100644 index 51eaa1a21..000000000 --- a/Sources/GRPCCore/Call/Client/ClientContext.swift +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -/// A context passed to the client containing additional information about the RPC. -public struct ClientContext: Sendable { - /// A description of the method being called. - public var descriptor: MethodDescriptor - - /// Create a new client interceptor context. - public init(descriptor: MethodDescriptor) { - self.descriptor = descriptor - } -} diff --git a/Sources/GRPCCore/Call/Client/ClientInterceptor.swift b/Sources/GRPCCore/Call/Client/ClientInterceptor.swift deleted file mode 100644 index 93ddf9cf5..000000000 --- a/Sources/GRPCCore/Call/Client/ClientInterceptor.swift +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -/// A type that intercepts requests and response for clients. -/// -/// Interceptors allow you to inspect and modify requests and responses. Requests are intercepted -/// before they are handed to a transport and responses are intercepted after they have been -/// received from the transport. They are typically used for cross-cutting concerns like injecting -/// metadata, validating messages, logging additional data, and tracing. -/// -/// Interceptors are registered with a client and apply to all RPCs. If you need to modify the -/// behavior of an interceptor on a per-RPC basis then you can use the -/// ``ClientContext/descriptor`` to determine which RPC is being called and -/// conditionalise behavior accordingly. -/// -/// - TODO: Update example and documentation to show how to register an interceptor. -/// -/// Some examples of simple interceptors follow. -/// -/// ## Metadata injection -/// -/// A common use-case for client interceptors is injecting metadata into a request. -/// -/// ```swift -/// struct MetadataInjectingClientInterceptor: ClientInterceptor { -/// let key: String -/// let fetchMetadata: @Sendable () async -> String -/// -/// func intercept( -/// request: ClientRequest.Stream, -/// context: ClientContext, -/// next: @Sendable ( -/// _ request: ClientRequest.Stream, -/// _ context: ClientContext -/// ) async throws -> ClientResponse.Stream -/// ) async throws -> ClientResponse.Stream { -/// // Fetch the metadata value and attach it. -/// let value = await self.fetchMetadata() -/// var request = request -/// request.metadata[self.key] = value -/// -/// // Forward the request to the next interceptor. -/// return try await next(request, context) -/// } -/// } -/// ``` -/// -/// Interceptors can also be used to print information about RPCs. -/// -/// ## Logging interceptor -/// -/// ```swift -/// struct LoggingClientInterceptor: ClientInterceptor { -/// func intercept( -/// request: ClientRequest.Stream, -/// context: ClientContext, -/// next: @Sendable ( -/// _ request: ClientRequest.Stream, -/// _ context: ClientContext -/// ) async throws -> ClientResponse.Stream -/// ) async throws -> ClientResponse.Stream { -/// print("Invoking method '\(context.descriptor)'") -/// let response = try await next(request, context) -/// -/// switch response.accepted { -/// case .success: -/// print("Server accepted RPC for processing") -/// case .failure(let error): -/// print("Server rejected RPC with error '\(error)'") -/// } -/// -/// return response -/// } -/// } -/// ``` -/// -/// For server-side interceptors see ``ServerInterceptor``. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -public protocol ClientInterceptor: Sendable { - /// Intercept a request object. - /// - /// - Parameters: - /// - request: The request object. - /// - context: Additional context about the request, including a descriptor - /// of the method being called. - /// - next: A closure to invoke to hand off the request and context to the next - /// interceptor in the chain. - /// - Returns: A response object. - func intercept( - request: ClientRequest.Stream, - context: ClientContext, - next: ( - _ request: ClientRequest.Stream, - _ context: ClientContext - ) async throws -> ClientResponse.Stream - ) async throws -> ClientResponse.Stream -} diff --git a/Sources/GRPCCore/Call/Client/ClientRequest.swift b/Sources/GRPCCore/Call/Client/ClientRequest.swift deleted file mode 100644 index 17e5e1077..000000000 --- a/Sources/GRPCCore/Call/Client/ClientRequest.swift +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -/// A namespace for request message types used by clients. -public enum ClientRequest {} - -extension ClientRequest { - /// A request created by the client for a single message. - /// - /// This is used for unary and server-streaming RPCs. - /// - /// See ``ClientRequest/Stream`` for streaming requests and ``ServerRequest/Single`` for the - /// servers representation of a single-message request. - /// - /// ## Creating ``Single`` requests - /// - /// ```swift - /// let request = ClientRequest.Single(message: "Hello, gRPC!") - /// print(request.metadata) // prints '[:]' - /// print(request.message) // prints 'Hello, gRPC!' - /// ``` - public struct Single: Sendable { - /// Caller-specified metadata to send to the server at the start of the RPC. - /// - /// Both gRPC Swift and its transport layer may insert additional metadata. Keys prefixed with - /// "grpc-" are prohibited and may result in undefined behaviour. Transports may also insert - /// their own metadata, you should avoid using key names which may clash with transport specific - /// metadata. Note that transports may also impose limits in the amount of metadata which may - /// be sent. - public var metadata: Metadata - - /// The message to send to the server. - public var message: Message - - /// Create a new single client request. - /// - /// - Parameters: - /// - message: The message to send to the server. - /// - metadata: Metadata to send to the server at the start of the request. Defaults to empty. - public init( - message: Message, - metadata: Metadata = [:] - ) { - self.metadata = metadata - self.message = message - } - } -} - -extension ClientRequest { - /// A request created by the client for a stream of messages. - /// - /// This is used for client-streaming and bidirectional-streaming RPCs. - /// - /// See ``ClientRequest/Single`` for single-message requests and ``ServerRequest/Stream`` for the - /// servers representation of a streaming-message request. - @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) - public struct Stream: Sendable { - /// Caller-specified metadata sent to the server at the start of the RPC. - /// - /// Both gRPC Swift and its transport layer may insert additional metadata. Keys prefixed with - /// "grpc-" are prohibited and may result in undefined behaviour. Transports may also insert - /// their own metadata, you should avoid using key names which may clash with transport specific - /// metadata. Note that transports may also impose limits in the amount of metadata which may - /// be sent. - public var metadata: Metadata - - /// A closure which, when called, writes messages in the writer. - /// - /// The producer will only be consumed once by gRPC and therefore isn't required to be - /// idempotent. If the producer throws an error then the RPC will be cancelled. Once the - /// producer returns the request stream is closed. - public var producer: @Sendable (RPCWriter) async throws -> Void - - /// Create a new streaming client request. - /// - /// - Parameters: - /// - messageType: The type of message contained in this request, defaults to `Message.self`. - /// - metadata: Metadata to send to the server at the start of the request. Defaults to empty. - /// - producer: A closure which writes messages to send to the server. The closure is called - /// at most once and may not be called. - public init( - of messageType: Message.Type = Message.self, - metadata: Metadata = [:], - producer: @escaping @Sendable (RPCWriter) async throws -> Void - ) { - self.metadata = metadata - self.producer = producer - } - } -} diff --git a/Sources/GRPCCore/Call/Client/ClientResponse.swift b/Sources/GRPCCore/Call/Client/ClientResponse.swift deleted file mode 100644 index a031933fd..000000000 --- a/Sources/GRPCCore/Call/Client/ClientResponse.swift +++ /dev/null @@ -1,394 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -/// A namespace for response message types used by clients. -public enum ClientResponse {} - -extension ClientResponse { - /// A response for a single message received by a client. - /// - /// Single responses are used for unary and client-streaming RPCs. For streaming responses - /// see ``ClientResponse/Stream``. - /// - /// A single response captures every part of the response stream and distinguishes successful - /// and unsuccessful responses via the ``accepted`` property. The value for the `success` case - /// contains the initial metadata, response message, and the trailing metadata and implicitly - /// has an ``Status/Code-swift.struct/ok`` status code. - /// - /// The `failure` case indicates that the server chose not to process the RPC, or the processing - /// of the RPC failed, or the client failed to execute the request. The failure case contains - /// an ``RPCError`` describing why the RPC failed, including an error code, error message and any - /// metadata sent by the server. - /// - /// ### Using ``Single`` responses - /// - /// Each response has a ``accepted`` property which contains all RPC information. You can create - /// one by calling ``init(accepted:)`` or one of the two convenience initializers: - /// - ``init(message:metadata:trailingMetadata:)`` to create a successful response, or - /// - ``init(of:error:)`` to create a failed response. - /// - /// You can interrogate a response by inspecting the ``accepted`` property directly or by using - /// its convenience properties: - /// - ``metadata`` extracts the initial metadata, - /// - ``message`` extracts the message, or throws if the response failed, and - /// - ``trailingMetadata`` extracts the trailing metadata. - /// - /// The following example demonstrates how you can use the API: - /// - /// ```swift - /// // Create a successful response - /// let response = ClientResponse.Single( - /// message: "Hello, World!", - /// metadata: ["hello": "initial metadata"], - /// trailingMetadata: ["goodbye": "trailing metadata"] - /// ) - /// - /// // The explicit API: - /// switch response { - /// case .success(let contents): - /// print("Received response with message '\(try contents.message.get())'") - /// case .failure(let error): - /// print("RPC failed with code '\(error.code)'") - /// } - /// - /// // The convenience API: - /// do { - /// print("Received response with message '\(try response.message)'") - /// } catch let error as RPCError { - /// print("RPC failed with code '\(error.code)'") - /// } - /// ``` - public struct Single: Sendable { - /// The contents of an accepted response with a single message. - public struct Contents: Sendable { - /// Metadata received from the server at the beginning of the response. - /// - /// The metadata may contain transport-specific information in addition to any application - /// level metadata provided by the service. - public var metadata: Metadata - - /// The response message received from the server, or an error of the RPC failed with a - /// non-ok status. - public var message: Result - - /// Metadata received from the server at the end of the response. - /// - /// The metadata may contain transport-specific information in addition to any application - /// level metadata provided by the service. - public var trailingMetadata: Metadata - - /// Creates a `Contents`. - /// - /// - Parameters: - /// - metadata: Metadata received from the server at the beginning of the response. - /// - message: The response message received from the server. - /// - trailingMetadata: Metadata received from the server at the end of the response. - public init( - metadata: Metadata, - message: Message, - trailingMetadata: Metadata - ) { - self.metadata = metadata - self.message = .success(message) - self.trailingMetadata = trailingMetadata - } - - /// Creates a `Contents`. - /// - /// - Parameters: - /// - metadata: Metadata received from the server at the beginning of the response. - /// - error: Error received from the server. - public init( - metadata: Metadata, - error: RPCError - ) { - self.metadata = metadata - self.message = .failure(error) - self.trailingMetadata = error.metadata - } - } - - /// Whether the RPC was accepted or rejected. - /// - /// The `success` case indicates the RPC completed successfully with an - /// ``Status/Code-swift.struct/ok`` status code. The `failure` case indicates that the RPC was - /// rejected by the server and wasn't processed or couldn't be processed successfully. - public var accepted: Result - - /// Creates a new response. - /// - /// - Parameter accepted: The result of the RPC. - public init(accepted: Result) { - self.accepted = accepted - } - } -} - -extension ClientResponse { - /// A response for a stream of messages received by a client. - /// - /// Stream responses are used for server-streaming and bidirectional-streaming RPCs. For single - /// responses see ``ClientResponse/Single``. - /// - /// A stream response captures every part of the response stream over time and distinguishes - /// accepted and rejected requests via the ``accepted`` property. An "accepted" request is one - /// where the the server responds with initial metadata and attempts to process the request. A - /// "rejected" request is one where the server responds with a status as the first and only - /// response part and doesn't process the request body. - /// - /// The value for the `success` case contains the initial metadata and a ``RPCAsyncSequence`` of - /// message parts (messages followed by a single status). If the sequence completes without - /// throwing then the response implicitly has an ``Status/Code-swift.struct/ok`` status code. - /// However, the response sequence may also throw an ``RPCError`` if the server fails to complete - /// processing the request. - /// - /// The `failure` case indicates that the server chose not to process the RPC or the client failed - /// to execute the request. The failure case contains an ``RPCError`` describing why the RPC - /// failed, including an error code, error message and any metadata sent by the server. - /// - /// ### Using ``Stream`` responses - /// - /// Each response has a ``accepted`` property which contains RPC information. You can create - /// one by calling ``init(accepted:)`` or one of the two convenience initializers: - /// - ``init(of:metadata:bodyParts:)`` to create an accepted response, or - /// - ``init(of:error:)`` to create a failed response. - /// - /// You can interrogate a response by inspecting the ``accepted`` property directly or by using - /// its convenience properties: - /// - ``metadata`` extracts the initial metadata, - /// - ``messages`` extracts the sequence of response message, or throws if the response failed. - /// - /// The following example demonstrates how you can use the API: - /// - /// ```swift - /// // Create a failed response - /// let response = ClientResponse.Stream( - /// of: String.self, - /// error: RPCError(code: .notFound, message: "The requested resource couldn't be located") - /// ) - /// - /// // The explicit API: - /// switch response { - /// case .success(let contents): - /// for try await part in contents.bodyParts { - /// switch part { - /// case .message(let message): - /// print("Received message '\(message)'") - /// case .trailingMetadata(let metadata): - /// print("Received trailing metadata '\(metadata)'") - /// } - /// } - /// case .failure(let error): - /// print("RPC failed with code '\(error.code)'") - /// } - /// - /// // The convenience API: - /// do { - /// for try await message in response.messages { - /// print("Received message '\(message)'") - /// } - /// } catch let error as RPCError { - /// print("RPC failed with code '\(error.code)'") - /// } - /// ``` - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public struct Stream: Sendable { - public struct Contents: Sendable { - /// Metadata received from the server at the beginning of the response. - /// - /// The metadata may contain transport-specific information in addition to any application - /// level metadata provided by the service. - public var metadata: Metadata - - /// A sequence of stream parts received from the server ending with metadata if the RPC - /// succeeded. - /// - /// If the RPC fails then the sequence will throw an ``RPCError``. - /// - /// The sequence may only be iterated once. - public var bodyParts: RPCAsyncSequence - - /// Parts received from the server. - public enum BodyPart: Sendable { - /// A response message. - case message(Message) - /// Metadata. Must be the final value of the sequence unless the stream throws an error. - case trailingMetadata(Metadata) - } - - /// Creates a ``Contents``. - /// - /// - Parameters: - /// - metadata: Metadata received from the server at the beginning of the response. - /// - bodyParts: An `AsyncSequence` of parts received from the server. - public init( - metadata: Metadata, - bodyParts: RPCAsyncSequence - ) { - self.metadata = metadata - self.bodyParts = bodyParts - } - } - - /// Whether the RPC was accepted or rejected. - /// - /// The `success` case indicates the RPC was accepted by the server for - /// processing, however, the RPC may still fail by throwing an error from its - /// `messages` sequence. The `failure` case indicates that the RPC was - /// rejected by the server. - public var accepted: Result - - /// Creates a new response. - /// - /// - Parameter accepted: The result of the RPC. - public init(accepted: Result) { - self.accepted = accepted - } - } -} - -// MARK: - Convenience API - -extension ClientResponse.Single { - /// Creates a new accepted response. - /// - /// - Parameters: - /// - metadata: Metadata received from the server at the beginning of the response. - /// - message: The response message received from the server. - /// - trailingMetadata: Metadata received from the server at the end of the response. - public init(message: Message, metadata: Metadata = [:], trailingMetadata: Metadata = [:]) { - let contents = Contents( - metadata: metadata, - message: message, - trailingMetadata: trailingMetadata - ) - self.accepted = .success(contents) - } - - /// Creates a new accepted response with a failed outcome. - /// - /// - Parameters: - /// - messageType: The type of message. - /// - metadata: Metadata received from the server at the beginning of the response. - /// - error: An error describing why the RPC failed. - public init(of messageType: Message.Type = Message.self, metadata: Metadata, error: RPCError) { - let contents = Contents(metadata: metadata, error: error) - self.accepted = .success(contents) - } - - /// Creates a new failed response. - /// - /// - Parameters: - /// - messageType: The type of message. - /// - error: An error describing why the RPC failed. - public init(of messageType: Message.Type = Message.self, error: RPCError) { - self.accepted = .failure(error) - } - - /// Returns metadata received from the server at the start of the response. - /// - /// For rejected RPCs (in other words, where ``accepted`` is `failure`) the metadata is empty. - public var metadata: Metadata { - switch self.accepted { - case let .success(contents): - return contents.metadata - case .failure: - return [:] - } - } - - /// Returns the message received from the server. - /// - /// - Throws: ``RPCError`` if the request failed. - public var message: Message { - get throws { - try self.accepted.flatMap { $0.message }.get() - } - } - - /// Returns metadata received from the server at the end of the response. - /// - /// Unlike ``metadata``, for rejected RPCs the metadata returned may contain values. - public var trailingMetadata: Metadata { - switch self.accepted { - case let .success(contents): - return contents.trailingMetadata - case let .failure(error): - return error.metadata - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension ClientResponse.Stream { - /// Creates a new accepted response. - /// - /// - Parameters: - /// - messageType: The type of message. - /// - metadata: Metadata received from the server at the beginning of the response. - /// - bodyParts: An ``RPCAsyncSequence`` of response parts received from the server. - public init( - of messageType: Message.Type = Message.self, - metadata: Metadata, - bodyParts: RPCAsyncSequence - ) { - let contents = Contents(metadata: metadata, bodyParts: bodyParts) - self.accepted = .success(contents) - } - - /// Creates a new failed response. - /// - /// - Parameters: - /// - messageType: The type of message. - /// - error: An error describing why the RPC failed. - public init(of messageType: Message.Type = Message.self, error: RPCError) { - self.accepted = .failure(error) - } - - /// Returns metadata received from the server at the start of the response. - /// - /// For rejected RPCs (in other words, where ``accepted`` is `failure`) the metadata is empty. - public var metadata: Metadata { - switch self.accepted { - case let .success(contents): - return contents.metadata - case .failure: - return [:] - } - } - - /// Returns the messages received from the server. - /// - /// For rejected RPCs the `RPCAsyncSequence` throws a `RPCError``. - public var messages: RPCAsyncSequence { - switch self.accepted { - case let .success(contents): - let filtered = contents.bodyParts.compactMap { - switch $0 { - case let .message(message): - return message - case .trailingMetadata: - return nil - } - } - - return RPCAsyncSequence(wrapping: filtered) - - case let .failure(error): - return RPCAsyncSequence.throwing(error) - } - } -} diff --git a/Sources/GRPCCore/Call/Client/Internal/ClientRPCExecutor+HedgingExecutor.swift b/Sources/GRPCCore/Call/Client/Internal/ClientRPCExecutor+HedgingExecutor.swift deleted file mode 100644 index bf57dae23..000000000 --- a/Sources/GRPCCore/Call/Client/Internal/ClientRPCExecutor+HedgingExecutor.swift +++ /dev/null @@ -1,575 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -public import Synchronization // would be internal but for usableFromInline - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension ClientRPCExecutor { - @usableFromInline - struct HedgingExecutor< - Transport: ClientTransport, - Input: Sendable, - Output: Sendable, - Serializer: MessageSerializer, - Deserializer: MessageDeserializer - >: Sendable where Serializer.Message == Input, Deserializer.Message == Output { - @usableFromInline - let transport: Transport - @usableFromInline - let policy: HedgingPolicy - @usableFromInline - let deadline: ContinuousClock.Instant? - @usableFromInline - let interceptors: [any ClientInterceptor] - @usableFromInline - let serializer: Serializer - @usableFromInline - let deserializer: Deserializer - @usableFromInline - let bufferSize: Int - - @inlinable - init( - transport: Transport, - policy: HedgingPolicy, - deadline: ContinuousClock.Instant?, - interceptors: [any ClientInterceptor], - serializer: Serializer, - deserializer: Deserializer, - bufferSize: Int - ) { - self.transport = transport - self.policy = policy - self.deadline = deadline - self.interceptors = interceptors - self.serializer = serializer - self.deserializer = deserializer - self.bufferSize = bufferSize - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension ClientRPCExecutor.HedgingExecutor { - @inlinable - func execute( - request: ClientRequest.Stream, - method: MethodDescriptor, - options: CallOptions, - responseHandler: @Sendable @escaping (ClientResponse.Stream) async throws -> R - ) async throws -> R { - // The high level approach is to have two levels of task group. In the outer level tasks are - // run to: - // - run a timeout task (if necessary), - // - run the request producer so that it writes into a broadcast sequence - // - run the inner task group. - // - // An inner task group runs a number of RPC attempts which may run concurrently. It's - // responsible for tracking the responses from the server, potentially using one and cancelling - // all other in flight attempts. Each attempt is started at a fixed interval unless the server - // explicitly overrides the period using "pushback". - let result = await withTaskGroup(of: _HedgingTaskResult.self) { group in - if let deadline = self.deadline { - group.addTask { - let result = await Result { - try await Task.sleep(until: deadline, clock: .continuous) - } - return .timedOut(result) - } - } - - // Play the original request into the broadcast sequence and construct a replayable request. - let broadcast = BroadcastAsyncSequence.makeStream(bufferSize: self.bufferSize) - group.addTask { - let result = await Result { - try await request.producer(RPCWriter(wrapping: broadcast.continuation)) - } - broadcast.continuation.finish(with: result) - return .finishedRequest(result) - } - - group.addTask { - let replayableRequest = ClientRequest.Stream(metadata: request.metadata) { writer in - try await writer.write(contentsOf: broadcast.stream) - } - - let result = await self.executeAttempt( - request: replayableRequest, - method: method, - options: options, - responseHandler: responseHandler - ) - - return .rpcHandled(result) - } - - for await event in group { - switch event { - case .timedOut(let result): - switch result { - case .success: - group.cancelAll() - case .failure: - () // Cancelled, ignore and keep looping. - } - - case .finishedRequest(let result): - switch result { - case .success: - () - case .failure: - group.cancelAll() - } - - case .rpcHandled(let result): - group.cancelAll() - return result - } - } - - fatalError("Internal inconsistency") - } - - return try result.get() - } - - @inlinable - func executeAttempt( - request: ClientRequest.Stream, - method: MethodDescriptor, - options: CallOptions, - responseHandler: @Sendable @escaping (ClientResponse.Stream) async throws -> R - ) async -> Result { - await withTaskGroup( - of: _HedgingAttemptTaskResult.self, - returning: Result.self - ) { group in - // The strategy here is to have two types of task running in the group: - // - To execute an RPC attempt. - // - To wait some time before starting the next attempt. - // - // As multiple attempts run concurrently, each attempt shares a broadcast sequence. - // When an attempt receives a usable response it will yield its attempt number into the - // sequence. Each attempt subgroup will also consume the sequence. If an attempt reads a - // value which is different to its attempt number then it will cancel itself. Each attempt - // returns back a handled response or the failed response (in case no attempts are - // successful). Failed responses may also impact when the next attempt is executed via - // server pushback. - let picker = BroadcastAsyncSequence.makeStream(of: Int.self, bufferSize: 2) - - // There's a potential race with attempts identifying that they are 'chosen'. Two attempts - // could succeed at the same time but, only one can yield first, the second wouldn't be aware - // of this. To avoid this each attempt goes via a state check before yielding to the sequence - // ensuring that only one response is used. (If this wasn't the case the response handler - // could be invoked more than once.) - let state = SharedState(policy: self.policy) - - // There's always a first attempt, safe to '!'. - let result = state.withState { $0.nextAttemptNumber()! } - - group.addTask { - let result = await self._startAttempt( - request: request, - method: method, - options: options, - attempt: result.nextAttempt, - state: state, - picker: picker, - responseHandler: responseHandler - ) - - return .attemptCompleted(result) - } - - // Schedule the second attempt. - var nextScheduledAttempt = ScheduledState() - if result.scheduleNext { - nextScheduledAttempt.schedule(in: &group, pushback: false, delay: self.policy.hedgingDelay) - } - - // Stop the most recent unusable response in case no response succeeds. - var unusableResponse: ClientResponse.Stream? - - while let next = await group.next() { - switch next { - case .scheduledAttemptFired(let outcome): - switch outcome { - case .ran: - // Start a new attempt and possibly schedule the next. - if let result = state.withState({ $0.nextAttemptNumber() }) { - group.addTask { - let result = await self._startAttempt( - request: request, - method: method, - options: options, - attempt: result.nextAttempt, - state: state, - picker: picker, - responseHandler: responseHandler - ) - return .attemptCompleted(result) - } - - // Schedule the next attempt. - if result.scheduleNext { - nextScheduledAttempt.schedule( - in: &group, - pushback: false, - delay: self.policy.hedgingDelay - ) - } - } - - case .cancelled: - () - } - - case .attemptPicked: - // Not used by this task group. - fatalError("Internal inconsistency") - - case .attemptCompleted(let outcome): - switch outcome { - case .usableResponse(let response): - // Note: we don't need to cancel other in-flight requests; they will communicate - // between themselves when one of them is chosen. - nextScheduledAttempt.cancel() - return response - - case .unusableResponse(let response, let pushback): - // Stash the unusable response. - unusableResponse = response - - switch pushback { - case .none: - // If the handle is for a pushback then don't cancel it or schedule a new timer. - if nextScheduledAttempt.hasPushbackHandle { - continue - } - - nextScheduledAttempt.cancel() - - if let result = state.withState({ $0.nextAttemptNumber() }) { - group.addTask { - let result = await self._startAttempt( - request: request, - method: method, - options: options, - attempt: result.nextAttempt, - state: state, - picker: picker, - responseHandler: responseHandler - ) - return .attemptCompleted(result) - } - - // Schedule the next retry. - if result.scheduleNext { - nextScheduledAttempt.schedule( - in: &group, - pushback: true, - delay: self.policy.hedgingDelay - ) - } - } - - case .retryAfter(let delay): - nextScheduledAttempt.schedule(in: &group, pushback: true, delay: delay) - - case .stopRetrying: - // Stop any new attempts from happening. Let any existing attempts play out. - nextScheduledAttempt.cancel() - } - - case .noStreamAvailable(let error): - group.cancelAll() - return .failure(error) - } - } - } - - // The group always has a task which returns a response. If it's an acceptable response it - // will be processed and returned in the preceding while loop, this path is therefore only - // reachable if there was an unusable response so the force unwrap is safe. - return await Result { - try await responseHandler(unusableResponse!) - } - } - } - - @inlinable - func _startAttempt( - request: ClientRequest.Stream, - method: MethodDescriptor, - options: CallOptions, - attempt: Int, - state: SharedState, - picker: (stream: BroadcastAsyncSequence, continuation: BroadcastAsyncSequence.Source), - responseHandler: @Sendable @escaping (ClientResponse.Stream) async throws -> R - ) async -> _HedgingAttemptTaskResult.AttemptResult { - do { - return try await self.transport.withStream( - descriptor: method, - options: options - ) { stream -> _HedgingAttemptTaskResult.AttemptResult in - return await withTaskGroup(of: _HedgingAttemptTaskResult.self) { group in - group.addTask { - do { - // The picker stream will have at most one element. - for try await selectedAttempt in picker.stream { - return .attemptPicked(selectedAttempt == attempt) - } - return .attemptPicked(false) - } catch { - return .attemptPicked(false) - } - } - - group.addTask { - let result = await withTaskGroup( - of: Void.self, - returning: _HedgingAttemptTaskResult.AttemptResult.self - ) { group in - var request = request - if let deadline = self.deadline { - request.metadata.timeout = ContinuousClock.now.duration(to: deadline) - } - - let response = await ClientRPCExecutor._execute( - in: &group, - request: request, - method: method, - attempt: attempt, - serializer: self.serializer, - deserializer: self.deserializer, - interceptors: self.interceptors, - stream: stream - ) - - switch response.accepted { - case .success: - self.transport.retryThrottle?.recordSuccess() - - if state.withState({ $0.receivedUsableResponse() }) { - try? await picker.continuation.write(attempt) - picker.continuation.finish() - let result = await Result { try await responseHandler(response) } - return .usableResponse(result) - } else { - // A different attempt succeeded before we were cancelled. Report this as unusable. - return .unusableResponse(response, .none) - } - - case .failure(let error): - group.cancelAll() - - if self.policy.nonFatalStatusCodes.contains(Status.Code(error.code)) { - // The response failed and the status code is non-fatal, we can make another attempt. - self.transport.retryThrottle?.recordFailure() - return .unusableResponse(response, error.metadata.retryPushback) - } else { - // A fatal error code counts as a success to the throttle. - self.transport.retryThrottle?.recordSuccess() - - if state.withState({ $0.receivedUsableResponse() }) { - try! await picker.continuation.write(attempt) - picker.continuation.finish() - let result = await Result { try await responseHandler(response) } - return .usableResponse(result) - } else { - // A different attempt succeeded before we were cancelled. Report this as unusable. - return .unusableResponse(response, .none) - } - } - } - } - - return .attemptCompleted(result) - } - - for await next in group { - switch next { - case .attemptPicked(let wasPicked): - if !wasPicked { - group.cancelAll() - } - - case .attemptCompleted(let result): - group.cancelAll() - return result - - case .scheduledAttemptFired: - // Not used by this task group. - fatalError("Internal inconsistency") - } - } - - // There's always a task to return a `.response` which we use as a signal to return from - // the task group in the preceding code. This is therefore unreachable. - fatalError("Internal inconsistency") - } - } - } catch { - return .noStreamAvailable(error) - } - } - - @usableFromInline - final class SharedState: Sendable { - @usableFromInline - let state: Mutex - - @inlinable - init(policy: HedgingPolicy) { - self.state = Mutex(State(policy: policy)) - } - - @inlinable - func withState(_ body: (inout State) -> ReturnType) -> ReturnType { - self.state.withLock { - body(&$0) - } - } - } - - @usableFromInline - struct State: Sendable { - @usableFromInline - let _maxAttempts: Int - @usableFromInline - private(set) var attempt: Int - @usableFromInline - private(set) var hasUsableResponse: Bool - - @inlinable - init(policy: HedgingPolicy) { - self._maxAttempts = policy.maxAttempts - self.attempt = 1 - self.hasUsableResponse = false - } - - @inlinable - mutating func receivedUsableResponse() -> Bool { - if self.hasUsableResponse { - return false - } else { - self.hasUsableResponse = true - return true - } - } - - @usableFromInline - struct NextAttemptResult: Sendable { - @usableFromInline - var nextAttempt: Int - @usableFromInline - var scheduleNext: Bool - - @inlinable - init(nextAttempt: Int, scheduleNext: Bool) { - self.nextAttempt = nextAttempt - self.scheduleNext = scheduleNext - } - } - - @inlinable - mutating func nextAttemptNumber() -> NextAttemptResult? { - if self.hasUsableResponse || self.attempt > self._maxAttempts { - return nil - } else { - let attempt = self.attempt - self.attempt += 1 - return NextAttemptResult( - nextAttempt: attempt, - scheduleNext: self.attempt <= self._maxAttempts - ) - } - } - } - - @usableFromInline - struct ScheduledState { - @usableFromInline - var _handle: CancellableTaskHandle? - @usableFromInline - var _isPushback: Bool - - @inlinable - var hasPushbackHandle: Bool { - self._handle != nil && self._isPushback - } - - @inlinable - init() { - self._handle = nil - self._isPushback = false - } - - @inlinable - mutating func cancel() { - self._handle?.cancel() - self._handle = nil - self._isPushback = false - } - - @inlinable - mutating func schedule( - in group: inout TaskGroup<_HedgingAttemptTaskResult>, - pushback: Bool, - delay: Duration - ) { - self._handle?.cancel() - self._isPushback = pushback - self._handle = group.addCancellableTask { - do { - try await Task.sleep(for: delay, clock: .continuous) - return .scheduledAttemptFired(.ran) - } catch { - return .scheduledAttemptFired(.cancelled) - } - } - } - } -} - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -@usableFromInline -enum _HedgingTaskResult: Sendable { - case rpcHandled(Result) - case finishedRequest(Result) - case timedOut(Result) -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -@usableFromInline -enum _HedgingAttemptTaskResult: Sendable { - case attemptPicked(Bool) - case attemptCompleted(AttemptResult) - case scheduledAttemptFired(ScheduleEvent) - - @usableFromInline - enum AttemptResult: Sendable { - case unusableResponse(ClientResponse.Stream, Metadata.RetryPushback?) - case usableResponse(Result) - case noStreamAvailable(any Error) - } - - @usableFromInline - enum ScheduleEvent: Sendable { - case ran - case cancelled - } -} diff --git a/Sources/GRPCCore/Call/Client/Internal/ClientRPCExecutor+OneShotExecutor.swift b/Sources/GRPCCore/Call/Client/Internal/ClientRPCExecutor+OneShotExecutor.swift deleted file mode 100644 index a1288999c..000000000 --- a/Sources/GRPCCore/Call/Client/Internal/ClientRPCExecutor+OneShotExecutor.swift +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension ClientRPCExecutor { - /// An executor for requests which doesn't apply retries or hedging. The request has just one - /// attempt at execution. - @usableFromInline - struct OneShotExecutor< - Transport: ClientTransport, - Input: Sendable, - Output: Sendable, - Serializer: MessageSerializer, - Deserializer: MessageDeserializer - >: Sendable where Serializer.Message == Input, Deserializer.Message == Output { - @usableFromInline - let transport: Transport - @usableFromInline - let deadline: ContinuousClock.Instant? - @usableFromInline - let interceptors: [any ClientInterceptor] - @usableFromInline - let serializer: Serializer - @usableFromInline - let deserializer: Deserializer - - @inlinable - init( - transport: Transport, - deadline: ContinuousClock.Instant?, - interceptors: [any ClientInterceptor], - serializer: Serializer, - deserializer: Deserializer - ) { - self.transport = transport - self.deadline = deadline - self.interceptors = interceptors - self.serializer = serializer - self.deserializer = deserializer - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension ClientRPCExecutor.OneShotExecutor { - @inlinable - func execute( - request: ClientRequest.Stream, - method: MethodDescriptor, - options: CallOptions, - responseHandler: @Sendable @escaping (ClientResponse.Stream) async throws -> R - ) async throws -> R { - let result: Result - - if let deadline = self.deadline { - var request = request - request.metadata.timeout = ContinuousClock.now.duration(to: deadline) - let immutableRequest = request - result = await withDeadline(deadline) { - await self._execute( - request: immutableRequest, - method: method, - options: options, - responseHandler: responseHandler - ) - } - } else { - result = await self._execute( - request: request, - method: method, - options: options, - responseHandler: responseHandler - ) - } - - return try result.get() - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension ClientRPCExecutor.OneShotExecutor { - @inlinable - func _execute( - request: ClientRequest.Stream, - method: MethodDescriptor, - options: CallOptions, - responseHandler: @Sendable @escaping (ClientResponse.Stream) async throws -> R - ) async -> Result { - return await withTaskGroup(of: Void.self, returning: Result.self) { group in - do { - return try await self.transport.withStream(descriptor: method, options: options) { stream in - let response = await ClientRPCExecutor._execute( - in: &group, - request: request, - method: method, - attempt: 1, - serializer: self.serializer, - deserializer: self.deserializer, - interceptors: self.interceptors, - stream: stream - ) - - let result = await Result { - try await responseHandler(response) - } - - // The user handler can finish before the stream. Cancel it if that's the case. - group.cancelAll() - - return result - } - } catch { - return .failure(error) - } - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -@inlinable -func withDeadline( - _ deadline: ContinuousClock.Instant, - execute: @Sendable @escaping () async -> Result -) async -> Result { - return await withTaskGroup(of: _DeadlineChildTaskResult.self) { group in - group.addTask { - do { - try await Task.sleep(until: deadline) - return .deadlinePassed - } catch { - return .timeoutCancelled - } - } - - group.addTask { - let result = await execute() - return .taskCompleted(result) - } - - while let next = await group.next() { - switch next { - case .deadlinePassed: - // Timeout expired; cancel the work. - group.cancelAll() - - case .timeoutCancelled: - () // Wait for more tasks to finish. - - case .taskCompleted(let result): - // The work finished. Cancel any remaining tasks. - group.cancelAll() - return result - } - } - - fatalError("Internal inconsistency") - } -} - -@usableFromInline -enum _DeadlineChildTaskResult: Sendable { - case deadlinePassed - case timeoutCancelled - case taskCompleted(Value) -} diff --git a/Sources/GRPCCore/Call/Client/Internal/ClientRPCExecutor+RetryExecutor.swift b/Sources/GRPCCore/Call/Client/Internal/ClientRPCExecutor+RetryExecutor.swift deleted file mode 100644 index d808b1f4a..000000000 --- a/Sources/GRPCCore/Call/Client/Internal/ClientRPCExecutor+RetryExecutor.swift +++ /dev/null @@ -1,312 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension ClientRPCExecutor { - @usableFromInline - struct RetryExecutor< - Transport: ClientTransport, - Input: Sendable, - Output: Sendable, - Serializer: MessageSerializer, - Deserializer: MessageDeserializer - >: Sendable where Serializer.Message == Input, Deserializer.Message == Output { - @usableFromInline - let transport: Transport - @usableFromInline - let policy: RetryPolicy - @usableFromInline - let deadline: ContinuousClock.Instant? - @usableFromInline - let interceptors: [any ClientInterceptor] - @usableFromInline - let serializer: Serializer - @usableFromInline - let deserializer: Deserializer - @usableFromInline - let bufferSize: Int - - @inlinable - init( - transport: Transport, - policy: RetryPolicy, - deadline: ContinuousClock.Instant?, - interceptors: [any ClientInterceptor], - serializer: Serializer, - deserializer: Deserializer, - bufferSize: Int - ) { - self.transport = transport - self.policy = policy - self.deadline = deadline - self.interceptors = interceptors - self.serializer = serializer - self.deserializer = deserializer - self.bufferSize = bufferSize - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension ClientRPCExecutor.RetryExecutor { - @inlinable - func execute( - request: ClientRequest.Stream, - method: MethodDescriptor, - options: CallOptions, - responseHandler: @Sendable @escaping (ClientResponse.Stream) async throws -> R - ) async throws -> R { - // There's quite a lot going on here... - // - // The high level approach is to have two levels of task group. In the outer level tasks are - // run to: - // - run a timeout task (if necessary), - // - run the request producer so that it writes into a broadcast sequence (in this instance we - // don't care about broadcasting but the sequence's ability to replay) - // - run the inner task group. - // - // An inner task group is run for each RPC attempt. We might also pause between attempts. The - // inner group runs two tasks: - // - a stream executor, and - // - the unsafe RPC executor which inspects the response, either passing it to the handler or - // deciding a retry should be undertaken. - // - // It is also worth noting that the server can override the retry delay using "pushback" and - // retries may be skipped if the throttle is applied. - let result = await withTaskGroup( - of: _RetryExecutorTask.self, - returning: Result.self - ) { group in - // Add a task to limit the overall execution time of the RPC. - if let deadline = self.deadline { - group.addTask { - let result = await Result { - try await Task.sleep(until: deadline, clock: .continuous) - } - return .timedOut(result) - } - } - - // Play the original request into the broadcast sequence and construct a replayable request. - let retry = BroadcastAsyncSequence.makeStream(bufferSize: self.bufferSize) - group.addTask { - let result = await Result { - try await request.producer(RPCWriter(wrapping: retry.continuation)) - } - retry.continuation.finish(with: result) - return .outboundFinished(result) - } - - // The sequence isn't limited by the number of attempts as the iterator is reset when the - // server applies pushback. - let delaySequence = RetryDelaySequence(policy: self.policy) - var delayIterator = delaySequence.makeIterator() - - for attempt in 1 ... self.policy.maxAttempts { - do { - let attemptResult = try await self.transport.withStream( - descriptor: method, - options: options - ) { stream in - group.addTask { - var metadata = request.metadata - // Work out the timeout from the deadline. - if let deadline = self.deadline { - metadata.timeout = ContinuousClock.now.duration(to: deadline) - } - - return await self.executeAttempt( - stream: stream, - metadata: metadata, - retryStream: retry.stream, - method: method, - attempt: attempt, - responseHandler: responseHandler - ) - } - - loop: while let next = await group.next() { - switch next { - case .handledResponse(let result): - // A usable response; cancel the remaining work and return the result. - group.cancelAll() - return Optional.some(result) - - case .retry(let delayOverride): - // The attempt failed, wait a bit and then retry. The server might have overridden the - // delay via pushback so preferentially use that value. - // - // Any error will come from cancellation: if it happens while we're sleeping we can - // just loop around, the next attempt will be cancelled immediately and we will return - // its response to the client. - if let delayOverride = delayOverride { - // If the delay is overridden with server pushback then reset the iterator for the - // next retry. - delayIterator = delaySequence.makeIterator() - try? await Task.sleep(until: .now.advanced(by: delayOverride), clock: .continuous) - } else { - // The delay iterator never terminates. - try? await Task.sleep( - until: .now.advanced(by: delayIterator.next()!), - clock: .continuous - ) - } - - break loop // from the while loop so another attempt can be started. - - case .timedOut(.success), .outboundFinished(.failure): - // Timeout task fired successfully or failed to process the outbound stream. Cancel and - // wait for a usable response (which is likely to be an error). - group.cancelAll() - - case .timedOut(.failure), .outboundFinished(.success): - // Timeout task failed which means it was cancelled (so no need to cancel again) or the - // outbound stream was successfully processed (so don't need to do anything). - () - } - } - return nil - } - - if let attemptResult { - return attemptResult - } - } catch { - return .failure(error) - } - } - fatalError("Internal inconsistency") - } - - return try result.get() - } - - @inlinable - func executeAttempt( - stream: RPCStream, - metadata: Metadata, - retryStream: BroadcastAsyncSequence, - method: MethodDescriptor, - attempt: Int, - responseHandler: @Sendable @escaping (ClientResponse.Stream) async throws -> R - ) async -> _RetryExecutorTask { - return await withTaskGroup( - of: Void.self, - returning: _RetryExecutorTask.self - ) { group in - let request = ClientRequest.Stream(metadata: metadata) { - try await $0.write(contentsOf: retryStream) - } - - let response = await ClientRPCExecutor._execute( - in: &group, - request: request, - method: method, - attempt: attempt, - serializer: self.serializer, - deserializer: self.deserializer, - interceptors: self.interceptors, - stream: stream - ) - - let shouldRetry: Bool - let retryDelayOverride: Duration? - - switch response.accepted { - case .success: - // Request was accepted. This counts as success to the throttle and there's no need - // to retry. - self.transport.retryThrottle?.recordSuccess() - retryDelayOverride = nil - shouldRetry = false - - case .failure(let error): - // The request was rejected. Determine whether a retry should be carried out. The - // following conditions must be checked: - // - // - Whether the status code is retryable. - // - Whether more attempts are permitted by the config. - // - Whether the throttle permits another retry to be carried out. - // - Whether the server pushed back to either stop further retries or to override - // the delay before the next retry. - let code = Status.Code(error.code) - let isRetryableStatusCode = self.policy.retryableStatusCodes.contains(code) - - if isRetryableStatusCode { - // Counted as failure for throttling. - let throttled = self.transport.retryThrottle?.recordFailure() ?? false - - // Status code can be retried, Did the server send pushback? - switch error.metadata.retryPushback { - case .retryAfter(let delay): - // Pushback: only retry if our config permits it. - shouldRetry = (attempt < self.policy.maxAttempts) && !throttled - retryDelayOverride = delay - case .stopRetrying: - // Server told us to stop trying. - shouldRetry = false - retryDelayOverride = nil - case .none: - // No pushback: only retry if our config permits it. - shouldRetry = (attempt < self.policy.maxAttempts) && !throttled - retryDelayOverride = nil - break - } - } else { - // Not-retryable; this is considered a success. - self.transport.retryThrottle?.recordSuccess() - shouldRetry = false - retryDelayOverride = nil - } - } - - if shouldRetry { - // Cancel subscribers of the broadcast sequence. This is safe as we are the only - // subscriber and maximises the chances that 'isKnownSafeForNextSubscriber' will - // return true. - // - // Note: this must only be called if we should retry, otherwise we may cancel a - // subscriber for an accepted request. - retryStream.invalidateAllSubscriptions() - - // Only retry if we know it's safe for the next subscriber, that is, the first - // element is still in the buffer. It's safe to call this because there's only - // ever one attempt at a time and the existing subscribers have been invalidated. - if retryStream.isKnownSafeForNextSubscriber { - return .retry(retryDelayOverride) - } - } - - // Not retrying or not safe to retry. - let result = await Result { - // Check for cancellation; the RPC may have timed out in which case we should skip - // the response handler. - try Task.checkCancellation() - return try await responseHandler(response) - } - return .handledResponse(result) - } - } -} - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -@usableFromInline -enum _RetryExecutorTask: Sendable { - case timedOut(Result) - case handledResponse(Result) - case retry(Duration?) - case outboundFinished(Result) -} diff --git a/Sources/GRPCCore/Call/Client/Internal/ClientRPCExecutor.swift b/Sources/GRPCCore/Call/Client/Internal/ClientRPCExecutor.swift deleted file mode 100644 index 33e46fd2d..000000000 --- a/Sources/GRPCCore/Call/Client/Internal/ClientRPCExecutor.swift +++ /dev/null @@ -1,197 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -@usableFromInline -enum ClientRPCExecutor { - /// Execute the request and handle its response. - /// - /// - Parameters: - /// - request: The request to execute. - /// - method: A description of the method to execute the request against. - /// - options: RPC options. - /// - serializer: A serializer to convert input messages to bytes. - /// - deserializer: A deserializer to convert bytes to output messages. - /// - transport: The transport to execute the request on. - /// - interceptors: An array of interceptors which the request and response pass through. The - /// interceptors will be called in the order of the array. - /// - handler: A closure for handling the response. Once the closure returns, any resources from - /// the RPC will be torn down. - /// - Returns: The result returns from the `handler`. - @inlinable - static func execute( - request: ClientRequest.Stream, - method: MethodDescriptor, - options: CallOptions, - serializer: some MessageSerializer, - deserializer: some MessageDeserializer, - transport: some ClientTransport, - interceptors: [any ClientInterceptor], - handler: @Sendable @escaping (ClientResponse.Stream) async throws -> Result - ) async throws -> Result { - let deadline = options.timeout.map { ContinuousClock.now + $0 } - - switch options.executionPolicy?.wrapped { - case .none: - let oneShotExecutor = OneShotExecutor( - transport: transport, - deadline: deadline, - interceptors: interceptors, - serializer: serializer, - deserializer: deserializer - ) - - return try await oneShotExecutor.execute( - request: request, - method: method, - options: options, - responseHandler: handler - ) - - case .retry(let policy): - let retryExecutor = RetryExecutor( - transport: transport, - policy: policy, - deadline: deadline, - interceptors: interceptors, - serializer: serializer, - deserializer: deserializer, - bufferSize: 64 // TODO: the client should have some control over this. - ) - - return try await retryExecutor.execute( - request: request, - method: method, - options: options, - responseHandler: handler - ) - - case .hedge(let policy): - let hedging = HedgingExecutor( - transport: transport, - policy: policy, - deadline: deadline, - interceptors: interceptors, - serializer: serializer, - deserializer: deserializer, - bufferSize: 64 // TODO: the client should have some control over this. - ) - - return try await hedging.execute( - request: request, - method: method, - options: options, - responseHandler: handler - ) - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension ClientRPCExecutor { - /// Executes a request on a given stream processor. - /// - /// - Parameters: - /// - request: The request to execute. - /// - method: A description of the method to execute the request against. - /// - attempt: The attempt number of the request. - /// - serializer: A serializer to convert input messages to bytes. - /// - deserializer: A deserializer to convert bytes to output messages. - /// - interceptors: An array of interceptors which the request and response pass through. The - /// interceptors will be called in the order of the array. - /// - Returns: The deserialized response. - @inlinable // would be private - static func _execute( - in group: inout TaskGroup, - request: ClientRequest.Stream, - method: MethodDescriptor, - attempt: Int, - serializer: some MessageSerializer, - deserializer: some MessageDeserializer, - interceptors: [any ClientInterceptor], - stream: RPCStream - ) async -> ClientResponse.Stream { - let context = ClientContext(descriptor: method) - - if interceptors.isEmpty { - return await ClientStreamExecutor.execute( - in: &group, - request: request, - context: context, - attempt: attempt, - serializer: serializer, - deserializer: deserializer, - stream: stream - ) - } else { - return await Self._intercept( - in: &group, - request: request, - context: context, - iterator: interceptors.makeIterator() - ) { group, request, context in - return await ClientStreamExecutor.execute( - in: &group, - request: request, - context: context, - attempt: attempt, - serializer: serializer, - deserializer: deserializer, - stream: stream - ) - } - } - } - - @inlinable - static func _intercept( - in group: inout TaskGroup, - request: ClientRequest.Stream, - context: ClientContext, - iterator: Array.Iterator, - finally: ( - _ group: inout TaskGroup, - _ request: ClientRequest.Stream, - _ context: ClientContext - ) async -> ClientResponse.Stream - ) async -> ClientResponse.Stream { - var iterator = iterator - - switch iterator.next() { - case .some(let interceptor): - let iter = iterator - do { - return try await interceptor.intercept(request: request, context: context) { - await self._intercept( - in: &group, - request: $0, - context: $1, - iterator: iter, - finally: finally - ) - } - } catch let error as RPCError { - return ClientResponse.Stream(error: error) - } catch let other { - let error = RPCError(code: .unknown, message: "", cause: other) - return ClientResponse.Stream(error: error) - } - - case .none: - return await finally(&group, request, context) - } - } -} diff --git a/Sources/GRPCCore/Call/Client/Internal/ClientRequest+Convenience.swift b/Sources/GRPCCore/Call/Client/Internal/ClientRequest+Convenience.swift deleted file mode 100644 index 74beb368b..000000000 --- a/Sources/GRPCCore/Call/Client/Internal/ClientRequest+Convenience.swift +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -extension ClientRequest.Stream { - internal init(single request: ClientRequest.Single) { - self.init(metadata: request.metadata) { - try await $0.write(request.message) - } - } -} diff --git a/Sources/GRPCCore/Call/Client/Internal/ClientResponse+Convenience.swift b/Sources/GRPCCore/Call/Client/Internal/ClientResponse+Convenience.swift deleted file mode 100644 index ecba7cf57..000000000 --- a/Sources/GRPCCore/Call/Client/Internal/ClientResponse+Convenience.swift +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension ClientResponse.Single { - /// Converts a streaming response into a single response. - /// - /// - Parameter response: The streaming response to convert. - init(stream response: ClientResponse.Stream) async { - switch response.accepted { - case .success(let contents): - do { - let metadata = contents.metadata - var iterator = contents.bodyParts.makeAsyncIterator() - - // Happy path: message, trailing metadata, nil. - let part1 = try await iterator.next() - let part2 = try await iterator.next() - let part3 = try await iterator.next() - - switch (part1, part2, part3) { - case (.some(.message(let message)), .some(.trailingMetadata(let trailingMetadata)), .none): - let contents = Contents( - metadata: metadata, - message: message, - trailingMetadata: trailingMetadata - ) - self.accepted = .success(contents) - - case (.some(.message), .some(.message), _): - let error = RPCError( - code: .unimplemented, - message: """ - Multiple messages received, but only one is expected. The server may have \ - incorrectly implemented the RPC or the client and server may have a different \ - opinion on whether this RPC streams responses. - """ - ) - self.accepted = .failure(error) - - case (.some(.trailingMetadata), .none, .none): - let error = RPCError( - code: .unimplemented, - message: "No messages received, exactly one was expected." - ) - self.accepted = .failure(error) - - case (_, _, _): - let error = RPCError( - code: .internalError, - message: """ - The stream from the client transport is invalid. This is likely to be an incorrectly \ - implemented transport. Received parts: \([part1, part2, part3])." - """ - ) - self.accepted = .failure(error) - } - } catch let error as RPCError { - // Known error type. - self.accepted = .success(Contents(metadata: contents.metadata, error: error)) - } catch { - // Unexpected, but should be handled nonetheless. - self.accepted = .failure(RPCError(code: .unknown, message: String(describing: error))) - } - - case .failure(let error): - self.accepted = .failure(error) - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension ClientResponse.Stream { - /// Creates a streaming response from the given status and metadata. - /// - /// If the ``Status`` has code ``Status/Code-swift.struct/ok`` then an accepted stream is created - /// containing only the provided metadata. Otherwise a failed response is returned with an error - /// created from the status and metadata. - /// - /// - Parameters: - /// - status: The status received from the server. - /// - metadata: The metadata received from the server. - @inlinable - init(status: Status, metadata: Metadata) { - if let error = RPCError(status: status, metadata: metadata) { - self.accepted = .failure(error) - } else { - self.accepted = .success(.init(metadata: [:], bodyParts: .one(.trailingMetadata(metadata)))) - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension ClientResponse.Stream { - /// Returns a new response which maps the messages of this response. - /// - /// - Parameter transform: The function to transform each message with. - /// - Returns: The new response. - @inlinable - func map( - _ transform: @escaping @Sendable (Message) throws -> Mapped - ) -> ClientResponse.Stream { - switch self.accepted { - case .success(let contents): - return ClientResponse.Stream( - metadata: self.metadata, - bodyParts: RPCAsyncSequence( - wrapping: contents.bodyParts.map { - switch $0 { - case .message(let message): - return .message(try transform(message)) - case .trailingMetadata(let metadata): - return .trailingMetadata(metadata) - } - } - ) - ) - - case .failure(let error): - return ClientResponse.Stream(accepted: .failure(error)) - } - } -} diff --git a/Sources/GRPCCore/Call/Client/Internal/ClientStreamExecutor.swift b/Sources/GRPCCore/Call/Client/Internal/ClientStreamExecutor.swift deleted file mode 100644 index 8b635bca8..000000000 --- a/Sources/GRPCCore/Call/Client/Internal/ClientStreamExecutor.swift +++ /dev/null @@ -1,246 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -@usableFromInline -internal enum ClientStreamExecutor { - /// Execute a request on the stream executor. - /// - /// - Parameters: - /// - request: A streaming request. - /// - method: A description of the method to call. - /// - context: The client context. - /// - attempt: The attempt number for the RPC that will be executed. - /// - serializer: A request serializer. - /// - deserializer: A response deserializer. - /// - stream: The stream to excecute the RPC on. - /// - Returns: A streamed response. - @inlinable - static func execute( - in group: inout TaskGroup, - request: ClientRequest.Stream, - context: ClientContext, - attempt: Int, - serializer: some MessageSerializer, - deserializer: some MessageDeserializer, - stream: RPCStream - ) async -> ClientResponse.Stream { - // Let the server know this is a retry. - var metadata = request.metadata - if attempt > 1 { - metadata.previousRPCAttempts = attempt &- 1 - } - - group.addTask { - await Self._processRequest(on: stream.outbound, request: request, serializer: serializer) - } - - let part = await Self._waitForFirstResponsePart(on: stream.inbound) - // Wait for the first response to determine how to handle the response. - switch part { - case .metadata(var metadata, let iterator): - // Attach the number of previous attempts, it can be useful information for callers. - if attempt > 1 { - metadata.previousRPCAttempts = attempt &- 1 - } - - let bodyParts = RawBodyPartToMessageSequence( - base: UncheckedAsyncIteratorSequence(iterator.wrappedValue), - deserializer: deserializer - ) - - // Expected happy case: the server is processing the request. - return ClientResponse.Stream( - metadata: metadata, - bodyParts: RPCAsyncSequence(wrapping: bodyParts) - ) - - case .status(let status, var metadata): - // Attach the number of previous attempts, it can be useful information for callers. - if attempt > 1 { - metadata.previousRPCAttempts = attempt &- 1 - } - - // Expected unhappy (but okay) case; the server rejected the request. - return ClientResponse.Stream(status: status, metadata: metadata) - - case .failed(let error): - // Very unhappy case: the server did something unexpected. - return ClientResponse.Stream(error: error) - } - } - - @inlinable // would be private - static func _processRequest( - on stream: some ClosableRPCWriterProtocol, - request: ClientRequest.Stream, - serializer: some MessageSerializer - ) async { - let result = await Result { - try await stream.write(.metadata(request.metadata)) - try await request.producer(.map(into: stream) { .message(try serializer.serialize($0)) }) - }.castError(to: RPCError.self) { other in - RPCError(code: .unknown, message: "Write failed.", cause: other) - } - - switch result { - case .success: - await stream.finish() - case .failure(let error): - await stream.finish(throwing: error) - } - } - - @usableFromInline - enum OnFirstResponsePart: Sendable { - case metadata(Metadata, UnsafeTransfer) - case status(Status, Metadata) - case failed(RPCError) - } - - @inlinable // would be private - static func _waitForFirstResponsePart( - on stream: ClientTransport.Inbound - ) async -> OnFirstResponsePart { - var iterator = stream.makeAsyncIterator() - let result = await Result { - switch try await iterator.next() { - case .metadata(let metadata): - return .metadata(metadata, UnsafeTransfer(iterator)) - - case .status(let status, let metadata): - return .status(status, metadata) - - case .message: - let error = RPCError( - code: .internalError, - message: """ - Invalid stream. The transport returned a message as the first element in the \ - stream, expected metadata. This is likely to be a transport-specific bug. - """ - ) - return .failed(error) - - case .none: - if Task.isCancelled { - throw CancellationError() - } else { - let error = RPCError( - code: .internalError, - message: """ - Invalid stream. The transport returned an empty stream. This is likely to be \ - a transport-specific bug. - """ - ) - return .failed(error) - } - } - }.castError(to: RPCError.self) { error in - RPCError( - code: .unknown, - message: "The transport threw an unexpected error.", - cause: error - ) - } - - switch result { - case .success(let firstPart): - return firstPart - case .failure(let error): - return .failed(error) - } - } - - @usableFromInline - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - struct RawBodyPartToMessageSequence< - Base: AsyncSequence, - Message: Sendable, - Deserializer: MessageDeserializer, - Failure: Error - >: AsyncSequence, Sendable where Base: Sendable { - @usableFromInline - typealias Element = AsyncIterator.Element - - @usableFromInline - let base: Base - @usableFromInline - let deserializer: Deserializer - - @inlinable - init(base: Base, deserializer: Deserializer) { - self.base = base - self.deserializer = deserializer - } - - @inlinable - func makeAsyncIterator() -> AsyncIterator { - AsyncIterator(base: self.base.makeAsyncIterator(), deserializer: self.deserializer) - } - - @usableFromInline - struct AsyncIterator: AsyncIteratorProtocol { - @usableFromInline - typealias Element = ClientResponse.Stream.Contents.BodyPart - - @usableFromInline - var base: Base.AsyncIterator - @usableFromInline - let deserializer: Deserializer - - @inlinable - init(base: Base.AsyncIterator, deserializer: Deserializer) { - self.base = base - self.deserializer = deserializer - } - - @inlinable - mutating func next( - isolation actor: isolated (any Actor)? - ) async throws(any Error) -> ClientResponse.Stream.Contents.BodyPart? { - guard let part = try await self.base.next(isolation: `actor`) else { return nil } - - switch part { - case .metadata(let metadata): - let error = RPCError( - code: .internalError, - message: """ - Received multiple sets of metadata from the transport. This is likely to be a \ - transport specific bug. Metadata received: '\(metadata)'. - """ - ) - throw error - - case .message(let bytes): - let message = try self.deserializer.deserialize(bytes) - return .message(message) - - case .status(let status, let metadata): - if let error = RPCError(status: status, metadata: metadata) { - throw error - } else { - return .trailingMetadata(metadata) - } - } - } - - @inlinable - mutating func next() async throws -> ClientResponse.Stream.Contents.BodyPart? { - try await self.next(isolation: nil) - } - } - } -} diff --git a/Sources/GRPCCore/Call/Client/Internal/RetryDelaySequence.swift b/Sources/GRPCCore/Call/Client/Internal/RetryDelaySequence.swift deleted file mode 100644 index d1ef417f3..000000000 --- a/Sources/GRPCCore/Call/Client/Internal/RetryDelaySequence.swift +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ -#if canImport(Darwin) -public import Darwin // should be @usableFromInline -#else -public import Glibc // should be @usableFromInline -#endif - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -@usableFromInline -struct RetryDelaySequence: Sequence { - @usableFromInline - typealias Element = Duration - - @usableFromInline - let policy: RetryPolicy - - @inlinable - init(policy: RetryPolicy) { - self.policy = policy - } - - @inlinable - func makeIterator() -> Iterator { - Iterator(policy: self.policy) - } - - @usableFromInline - struct Iterator: IteratorProtocol { - @usableFromInline - let policy: RetryPolicy - @usableFromInline - private(set) var n = 1 - - @inlinable - init(policy: RetryPolicy) { - self.policy = policy - } - - @inlinable - var _initialBackoffSeconds: Double { - Self._durationToTimeInterval(self.policy.initialBackoff) - } - - @inlinable - var _maxBackoffSeconds: Double { - Self._durationToTimeInterval(self.policy.maxBackoff) - } - - @inlinable - mutating func next() -> Duration? { - defer { self.n += 1 } - - /// The nth retry will happen after a randomly chosen delay between zero and - /// `min(initialBackoff * backoffMultiplier^(n-1), maxBackoff)`. - let factor = pow(self.policy.backoffMultiplier, Double(self.n - 1)) - let computedBackoff = self._initialBackoffSeconds * factor - let clampedBackoff = Swift.min(computedBackoff, self._maxBackoffSeconds) - let randomisedBackoff = Double.random(in: 0.0 ... clampedBackoff) - - return Self._timeIntervalToDuration(randomisedBackoff) - } - - @inlinable - static func _timeIntervalToDuration(_ seconds: Double) -> Duration { - let secondsComponent = Int64(seconds) - let attoseconds = (seconds - Double(secondsComponent)) * 1e18 - let attosecondsComponent = Int64(attoseconds) - return Duration( - secondsComponent: secondsComponent, - attosecondsComponent: attosecondsComponent - ) - } - - @inlinable - static func _durationToTimeInterval(_ duration: Duration) -> Double { - var seconds = Double(duration.components.seconds) - seconds += (Double(duration.components.attoseconds) / 1e18) - return seconds - } - } -} diff --git a/Sources/GRPCCore/Call/Server/Internal/ServerRPCExecutor.swift b/Sources/GRPCCore/Call/Server/Internal/ServerRPCExecutor.swift deleted file mode 100644 index a67bfbe37..000000000 --- a/Sources/GRPCCore/Call/Server/Internal/ServerRPCExecutor.swift +++ /dev/null @@ -1,349 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -@usableFromInline -struct ServerRPCExecutor { - /// Executes an RPC using the provided handler. - /// - /// - Parameters: - /// - context: The context for the RPC. - /// - stream: The accepted stream to execute the RPC on. - /// - deserializer: A deserializer for messages received from the client. - /// - serializer: A serializer for messages to send to the client. - /// - interceptors: Server interceptors to apply to this RPC. - /// - handler: A handler which turns the request into a response. - @inlinable - static func execute( - context: ServerContext, - stream: RPCStream< - RPCAsyncSequence, - RPCWriter.Closable - >, - deserializer: some MessageDeserializer, - serializer: some MessageSerializer, - interceptors: [any ServerInterceptor], - handler: @Sendable @escaping ( - _ request: ServerRequest.Stream, - _ context: ServerContext - ) async throws -> ServerResponse.Stream - ) async { - // Wait for the first request part from the transport. - let firstPart = await Self._waitForFirstRequestPart(inbound: stream.inbound) - - switch firstPart { - case .process(let metadata, let inbound): - await Self._execute( - context: context, - metadata: metadata, - inbound: inbound, - outbound: stream.outbound, - deserializer: deserializer, - serializer: serializer, - interceptors: interceptors, - handler: handler - ) - - case .reject(let error): - // Stream can't be handled; write an error status and close. - let status = Status(code: Status.Code(error.code), message: error.message) - try? await stream.outbound.write(.status(status, error.metadata)) - await stream.outbound.finish() - } - } - - @inlinable - static func _execute( - context: ServerContext, - metadata: Metadata, - inbound: UnsafeTransfer.AsyncIterator>, - outbound: RPCWriter.Closable, - deserializer: some MessageDeserializer, - serializer: some MessageSerializer, - interceptors: [any ServerInterceptor], - handler: @escaping @Sendable ( - _ request: ServerRequest.Stream, - _ context: ServerContext - ) async throws -> ServerResponse.Stream - ) async { - if let timeout = metadata.timeout { - await Self._processRPCWithTimeout( - timeout: timeout, - context: context, - metadata: metadata, - inbound: inbound, - outbound: outbound, - deserializer: deserializer, - serializer: serializer, - interceptors: interceptors, - handler: handler - ) - } else { - await Self._processRPC( - context: context, - metadata: metadata, - inbound: inbound, - outbound: outbound, - deserializer: deserializer, - serializer: serializer, - interceptors: interceptors, - handler: handler - ) - } - } - - @inlinable - static func _processRPCWithTimeout( - timeout: Duration, - context: ServerContext, - metadata: Metadata, - inbound: UnsafeTransfer.AsyncIterator>, - outbound: RPCWriter.Closable, - deserializer: some MessageDeserializer, - serializer: some MessageSerializer, - interceptors: [any ServerInterceptor], - handler: @escaping @Sendable ( - _ request: ServerRequest.Stream, - _ context: ServerContext - ) async throws -> ServerResponse.Stream - ) async { - await withTaskGroup(of: ServerExecutorTask.self) { group in - group.addTask { - let result = await Result { - try await Task.sleep(for: timeout, clock: .continuous) - } - return .timedOut(result) - } - - group.addTask { - await Self._processRPC( - context: context, - metadata: metadata, - inbound: inbound, - outbound: outbound, - deserializer: deserializer, - serializer: serializer, - interceptors: interceptors, - handler: handler - ) - return .executed - } - - while let next = await group.next() { - switch next { - case .timedOut(.success): - // Timeout expired; cancel the work. - group.cancelAll() - - case .timedOut(.failure): - // Timeout failed (because it was cancelled). Wait for more tasks to finish. - () - - case .executed: - // The work finished. Cancel any remaining tasks. - group.cancelAll() - } - } - } - } - - @inlinable - static func _processRPC( - context: ServerContext, - metadata: Metadata, - inbound: UnsafeTransfer.AsyncIterator>, - outbound: RPCWriter.Closable, - deserializer: some MessageDeserializer, - serializer: some MessageSerializer, - interceptors: [any ServerInterceptor], - handler: @escaping @Sendable ( - _ request: ServerRequest.Stream, - _ context: ServerContext - ) async throws -> ServerResponse.Stream - ) async { - let messages = UncheckedAsyncIteratorSequence(inbound.wrappedValue).map { part in - switch part { - case .message(let bytes): - return try deserializer.deserialize(bytes) - case .metadata: - throw RPCError( - code: .internalError, - message: """ - Server received an extra set of metadata. Only one set of metadata may be received \ - at the start of the RPC. This is likely to be caused by a misbehaving client. - """ - ) - } - } - - let response = await Result { - // Run the request through the interceptors, finally passing it to the handler. - return try await Self._intercept( - request: ServerRequest.Stream( - metadata: metadata, - messages: RPCAsyncSequence(wrapping: messages) - ), - context: context, - interceptors: interceptors - ) { request, context in - try await handler(request, context) - } - }.castError(to: RPCError.self) { error in - RPCError(code: .unknown, message: "Service method threw an unknown error.", cause: error) - }.flatMap { response in - response.accepted - } - - let status: Status - let metadata: Metadata - - switch response { - case .success(let contents): - let result = await Result { - // Write the metadata and run the producer. - try await outbound.write(.metadata(contents.metadata)) - return try await contents.producer( - .serializingToRPCResponsePart(into: outbound, with: serializer) - ) - }.castError(to: RPCError.self) { error in - RPCError(code: .unknown, message: "", cause: error) - } - - switch result { - case .success(let trailingMetadata): - status = .ok - metadata = trailingMetadata - case .failure(let error): - status = Status(code: Status.Code(error.code), message: error.message) - metadata = error.metadata - } - - case .failure(let error): - status = Status(code: Status.Code(error.code), message: error.message) - metadata = error.metadata - } - - try? await outbound.write(.status(status, metadata)) - await outbound.finish() - } - - @inlinable - static func _waitForFirstRequestPart( - inbound: RPCAsyncSequence - ) async -> OnFirstRequestPart { - var iterator = inbound.makeAsyncIterator() - let part = await Result { try await iterator.next() } - let onFirstRequestPart: OnFirstRequestPart - - switch part { - case .success(.metadata(let metadata)): - // The only valid first part. - onFirstRequestPart = .process(metadata, UnsafeTransfer(iterator)) - - case .success(.none): - // Empty stream; reject. - let error = RPCError(code: .internalError, message: "Empty inbound server stream.") - onFirstRequestPart = .reject(error) - - case .success(.message): - let error = RPCError( - code: .internalError, - message: """ - Invalid inbound server stream; received message bytes at start of stream. This is \ - likely to be a transport specific bug. - """ - ) - onFirstRequestPart = .reject(error) - - case .failure(let error): - let error = RPCError( - code: .unknown, - message: "Inbound server stream threw error when reading metadata.", - cause: error - ) - onFirstRequestPart = .reject(error) - } - - return onFirstRequestPart - } - - @usableFromInline - enum OnFirstRequestPart { - case process( - Metadata, - UnsafeTransfer.AsyncIterator> - ) - case reject(RPCError) - } - - @usableFromInline - enum ServerExecutorTask: Sendable { - case timedOut(Result) - case executed - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension ServerRPCExecutor { - @inlinable - static func _intercept( - request: ServerRequest.Stream, - context: ServerContext, - interceptors: [any ServerInterceptor], - finally: @escaping @Sendable ( - _ request: ServerRequest.Stream, - _ context: ServerContext - ) async throws -> ServerResponse.Stream - ) async throws -> ServerResponse.Stream { - return try await self._intercept( - request: request, - context: context, - iterator: interceptors.makeIterator(), - finally: finally - ) - } - - @inlinable - static func _intercept( - request: ServerRequest.Stream, - context: ServerContext, - iterator: Array.Iterator, - finally: @escaping @Sendable ( - _ request: ServerRequest.Stream, - _ context: ServerContext - ) async throws -> ServerResponse.Stream - ) async throws -> ServerResponse.Stream { - var iterator = iterator - - switch iterator.next() { - case .some(let interceptor): - let iter = iterator - do { - return try await interceptor.intercept(request: request, context: context) { - try await self._intercept(request: $0, context: $1, iterator: iter, finally: finally) - } - } catch let error as RPCError { - return ServerResponse.Stream(error: error) - } catch let other { - let error = RPCError(code: .unknown, message: "", cause: other) - return ServerResponse.Stream(error: error) - } - - case .none: - return try await finally(request, context) - } - } -} diff --git a/Sources/GRPCCore/Call/Server/RPCRouter.swift b/Sources/GRPCCore/Call/Server/RPCRouter.swift deleted file mode 100644 index bc2f58fef..000000000 --- a/Sources/GRPCCore/Call/Server/RPCRouter.swift +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -/// Stores and provides handlers for RPCs. -/// -/// The router stores a handler for each RPC it knows about. Each handler encapsulate the business -/// logic for the RPC which is typically implemented by service owners. To register a handler you -/// can call ``registerHandler(forMethod:deserializer:serializer:handler:)``. You can check whether -/// the router has a handler for a method with ``hasHandler(forMethod:)`` or get a list of all -/// methods with handlers registered by calling ``methods``. You can also remove the handler for a -/// given method by calling ``removeHandler(forMethod:)``. -/// -/// In most cases you won't need to interact with the router directly. Instead you should register -/// your services with ``GRPCServer/init(transport:services:interceptors:)`` which will in turn -/// register each method with the router. -/// -/// You may wish to not serve all methods from your service in which case you can either: -/// -/// 1. Remove individual methods by calling ``removeHandler(forMethod:)``, or -/// 2. Implement ``RegistrableRPCService/registerMethods(with:)`` to register only the methods you -/// want to be served. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -public struct RPCRouter: Sendable { - @usableFromInline - struct RPCHandler: Sendable { - @usableFromInline - let _fn: - @Sendable ( - _ stream: RPCStream< - RPCAsyncSequence, - RPCWriter.Closable - >, - _ context: ServerContext, - _ interceptors: [any ServerInterceptor] - ) async -> Void - - @inlinable - init( - method: MethodDescriptor, - deserializer: some MessageDeserializer, - serializer: some MessageSerializer, - handler: @Sendable @escaping ( - _ request: ServerRequest.Stream, - _ context: ServerContext - ) async throws -> ServerResponse.Stream - ) { - self._fn = { stream, context, interceptors in - await ServerRPCExecutor.execute( - context: context, - stream: stream, - deserializer: deserializer, - serializer: serializer, - interceptors: interceptors, - handler: handler - ) - } - } - - @inlinable - func handle( - stream: RPCStream< - RPCAsyncSequence, - RPCWriter.Closable - >, - context: ServerContext, - interceptors: [any ServerInterceptor] - ) async { - await self._fn(stream, context, interceptors) - } - } - - @usableFromInline - private(set) var handlers: [MethodDescriptor: RPCHandler] - - /// Creates a new router with no methods registered. - public init() { - self.handlers = [:] - } - - /// Returns all descriptors known to the router in an undefined order. - public var methods: [MethodDescriptor] { - Array(self.handlers.keys) - } - - /// Returns the number of methods registered with the router. - public var count: Int { - self.handlers.count - } - - /// Returns whether a handler exists for a given method. - /// - /// - Parameter descriptor: A descriptor of the method. - /// - Returns: Whether a handler exists for the method. - public func hasHandler(forMethod descriptor: MethodDescriptor) -> Bool { - return self.handlers.keys.contains(descriptor) - } - - /// Registers a handler with the router. - /// - /// - Note: if a handler already exists for a given method then it will be replaced. - /// - /// - Parameters: - /// - descriptor: A descriptor for the method to register a handler for. - /// - deserializer: A deserializer to deserialize input messages received from the client. - /// - serializer: A serializer to serialize output messages to send to the client. - /// - handler: The function which handles the request and returns a response. - @inlinable - public mutating func registerHandler( - forMethod descriptor: MethodDescriptor, - deserializer: some MessageDeserializer, - serializer: some MessageSerializer, - handler: @Sendable @escaping ( - _ request: ServerRequest.Stream, - _ context: ServerContext - ) async throws -> ServerResponse.Stream - ) { - self.handlers[descriptor] = RPCHandler( - method: descriptor, - deserializer: deserializer, - serializer: serializer, - handler: handler - ) - } - - /// Removes any handler registered for the specified method. - /// - /// - Parameter descriptor: A descriptor of the method to remove a handler for. - /// - Returns: Whether a handler was removed. - @discardableResult - public mutating func removeHandler(forMethod descriptor: MethodDescriptor) -> Bool { - return self.handlers.removeValue(forKey: descriptor) != nil - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension RPCRouter { - internal func handle( - stream: RPCStream< - RPCAsyncSequence, - RPCWriter.Closable - >, - context: ServerContext, - interceptors: [any ServerInterceptor] - ) async { - if let handler = self.handlers[stream.descriptor] { - await handler.handle(stream: stream, context: context, interceptors: interceptors) - } else { - // If this throws then the stream must be closed which we can't do anything about, so ignore - // any error. - try? await stream.outbound.write(.status(.rpcNotImplemented, [:])) - await stream.outbound.finish() - } - } -} - -extension Status { - fileprivate static let rpcNotImplemented = Status( - code: .unimplemented, - message: "Requested RPC isn't implemented by this server." - ) -} diff --git a/Sources/GRPCCore/Call/Server/RegistrableRPCService.swift b/Sources/GRPCCore/Call/Server/RegistrableRPCService.swift deleted file mode 100644 index d9236c75b..000000000 --- a/Sources/GRPCCore/Call/Server/RegistrableRPCService.swift +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -/// An RPC service which can register its methods with an ``RPCRouter``. -/// -/// You typically won't have to implement this protocol yourself as the generated service code -/// provides conformance for your generated service type. However, if you need to customise which -/// methods your service offers or how the methods are registered then you can override the -/// generated conformance by implementing ``registerMethods(with:)`` manually by calling -/// ``RPCRouter/registerHandler(forMethod:deserializer:serializer:handler:)`` for each method -/// you want to register with the router. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -public protocol RegistrableRPCService: Sendable { - /// Registers methods to server with the provided ``RPCRouter``. - /// - /// - Parameter router: The router to register methods with. - func registerMethods(with router: inout RPCRouter) -} diff --git a/Sources/GRPCCore/Call/Server/ServerContext.swift b/Sources/GRPCCore/Call/Server/ServerContext.swift deleted file mode 100644 index a11f09acb..000000000 --- a/Sources/GRPCCore/Call/Server/ServerContext.swift +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -/// Additional information about an RPC handled by a server. -public struct ServerContext: Sendable { - /// A description of the method being called. - public var descriptor: MethodDescriptor - - /// Create a new server context. - public init(descriptor: MethodDescriptor) { - self.descriptor = descriptor - } -} diff --git a/Sources/GRPCCore/Call/Server/ServerInterceptor.swift b/Sources/GRPCCore/Call/Server/ServerInterceptor.swift deleted file mode 100644 index 2243fe8f2..000000000 --- a/Sources/GRPCCore/Call/Server/ServerInterceptor.swift +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -/// A type that intercepts requests and response for server. -/// -/// Interceptors allow you to inspect and modify requests and responses. Requests are intercepted -/// after they have been received by the transport and responses are intercepted after they have -/// been returned from a service. They are typically used for cross-cutting concerns like filtering -/// requests, validating messages, logging additional data, and tracing. -/// -/// Interceptors are registered with the server apply to all RPCs. If you need to modify the -/// behavior of an interceptor on a per-RPC basis then you can use the -/// ``ServerInterceptorContext/descriptor`` to determine which RPC is being called and -/// conditionalise behavior accordingly. -/// -/// - TODO: Update example and documentation to show how to register an interceptor. -/// -/// ## RPC filtering -/// -/// A common use of server-side interceptors is to filter requests from clients. Interceptors can -/// reject requests which are invalid without service code being called. The following example -/// demonstrates this. -/// -/// ```swift -/// struct AuthServerInterceptor: Sendable { -/// let isAuthorized: @Sendable (String, MethodDescriptor) async throws -> Void -/// -/// func intercept( -/// request: ServerRequest.Stream, -/// context: ServerInterceptorContext, -/// next: @Sendable ( -/// _ request: ServerRequest.Stream, -/// _ context: ServerInterceptorContext -/// ) async throws -> ServerResponse.Stream -/// ) async throws -> ServerResponse.Stream { -/// // Extract the auth token. -/// guard let token = request.metadata["authorization"] else { -/// throw RPCError(code: .unauthenticated, message: "Not authenticated") -/// } -/// -/// // Check whether it's valid. -/// try await self.isAuthorized(token, context.descriptor) -/// -/// // Forward the request. -/// return try await next(request, context) -/// } -/// } -/// ``` -/// -/// For client-side interceptors see ``ClientInterceptor``. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -public protocol ServerInterceptor: Sendable { - /// Intercept a request object. - /// - /// - Parameters: - /// - request: The request object. - /// - context: Additional context about the request, including a descriptor - /// of the method being called. - /// - next: A closure to invoke to hand off the request and context to the next - /// interceptor in the chain. - /// - Returns: A response object. - func intercept( - request: ServerRequest.Stream, - context: ServerContext, - next: @Sendable ( - _ request: ServerRequest.Stream, - _ context: ServerContext - ) async throws -> ServerResponse.Stream - ) async throws -> ServerResponse.Stream -} diff --git a/Sources/GRPCCore/Call/Server/ServerRequest.swift b/Sources/GRPCCore/Call/Server/ServerRequest.swift deleted file mode 100644 index 90618bdfe..000000000 --- a/Sources/GRPCCore/Call/Server/ServerRequest.swift +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -/// A namespace for request message types used by servers. -public enum ServerRequest {} - -extension ServerRequest { - /// A request received at the server containing a single message. - public struct Single: Sendable { - /// Metadata received from the client at the start of the RPC. - /// - /// The metadata contains gRPC and transport specific entries in addition to user-specified - /// metadata. - public var metadata: Metadata - - /// The message received from the client. - public var message: Message - - /// Create a new single server request. - /// - /// - Parameters: - /// - metadata: Metadata received from the client. - /// - message: The message received from the client. - public init(metadata: Metadata, message: Message) { - self.metadata = metadata - self.message = message - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension ServerRequest { - /// A request received at the server containing a stream of messages. - public struct Stream: Sendable { - /// Metadata received from the client at the start of the RPC. - /// - /// The metadata contains gRPC and transport specific entries in addition to user-specified - /// metadata. - public var metadata: Metadata - - /// A sequence of messages received from the client. - /// - /// The sequence may be iterated at most once. - public var messages: RPCAsyncSequence - - /// Create a new streaming request. - /// - /// - Parameters: - /// - metadata: Metadata received from the client. - /// - messages: A sequence of messages received from the client. - public init(metadata: Metadata, messages: RPCAsyncSequence) { - self.metadata = metadata - self.messages = messages - } - } -} - -// MARK: - Conversion - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension ServerRequest.Stream { - public init(single request: ServerRequest.Single) { - self.init(metadata: request.metadata, messages: .one(request.message)) - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension ServerRequest.Single { - public init(stream request: ServerRequest.Stream) async throws { - var iterator = request.messages.makeAsyncIterator() - - guard let message = try await iterator.next() else { - throw RPCError(code: .internalError, message: "Empty stream.") - } - - guard try await iterator.next() == nil else { - throw RPCError(code: .internalError, message: "Too many messages.") - } - - self = ServerRequest.Single(metadata: request.metadata, message: message) - } -} diff --git a/Sources/GRPCCore/Call/Server/ServerResponse.swift b/Sources/GRPCCore/Call/Server/ServerResponse.swift deleted file mode 100644 index a0b516815..000000000 --- a/Sources/GRPCCore/Call/Server/ServerResponse.swift +++ /dev/null @@ -1,343 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -/// A namespace for response message types used by servers. -public enum ServerResponse {} - -extension ServerResponse { - /// A response for a single message sent by a server. - /// - /// Single responses are used for unary and client-streaming RPCs. For streaming responses - /// see ``ServerResponse/Stream``. - /// - /// A single response captures every part of the response stream and distinguishes successful - /// and unsuccessful responses via the ``accepted`` property. The value for the `success` case - /// contains the initial metadata, response message, and the trailing metadata and implicitly - /// has an ``Status/Code-swift.struct/ok`` status code. - /// - /// The `failure` case indicates that the server chose not to process the RPC, or the processing - /// of the RPC failed. The failure case contains an ``RPCError`` describing why the RPC failed, - /// including an error code, error message and any metadata sent by the server. - /// - /// ### Using ``Single`` responses - /// - /// Each response has an ``accepted`` property which contains all RPC information. You can create - /// one by calling ``init(accepted:)`` or one of the two convenience initializers: - /// - ``init(message:metadata:trailingMetadata:)`` to create a successful response, or - /// - ``init(of:error:)`` to create a failed response. - /// - /// You can interrogate a response by inspecting the ``accepted`` property directly or by using - /// its convenience properties: - /// - ``metadata`` extracts the initial metadata, - /// - ``message`` extracts the message, or throws if the response failed, and - /// - ``trailingMetadata`` extracts the trailing metadata. - /// - /// The following example demonstrates how you can use the API: - /// - /// ```swift - /// // Create a successful response - /// let response = ServerResponse.Single( - /// message: "Hello, World!", - /// metadata: ["hello": "initial metadata"], - /// trailingMetadata: ["goodbye": "trailing metadata"] - /// ) - /// - /// // The explicit API: - /// switch response { - /// case .success(let contents): - /// print("Received response with message '\(contents.message)'") - /// case .failure(let error): - /// print("RPC failed with code '\(error.code)'") - /// } - /// - /// // The convenience API: - /// do { - /// print("Received response with message '\(try response.message)'") - /// } catch let error as RPCError { - /// print("RPC failed with code '\(error.code)'") - /// } - /// ``` - public struct Single: Sendable { - /// An accepted RPC with a successful outcome. - public struct Contents: Sendable { - /// Caller-specified metadata to send to the client at the start of the response. - /// - /// Both gRPC Swift and its transport layer may insert additional metadata. Keys prefixed with - /// "grpc-" are prohibited and may result in undefined behaviour. Transports may also insert - /// their own metadata, you should avoid using key names which may clash with transport - /// specific metadata. Note that transports may also impose limits in the amount of metadata - /// which may be sent. - public var metadata: Metadata - - /// The message to send to the client. - public var message: Message - - /// Caller-specified metadata to send to the client at the end of the response. - /// - /// Both gRPC Swift and its transport layer may insert additional metadata. Keys prefixed with - /// "grpc-" are prohibited and may result in undefined behaviour. Transports may also insert - /// their own metadata, you should avoid using key names which may clash with transport - /// specific metadata. Note that transports may also impose limits in the amount of metadata - /// which may be sent. - public var trailingMetadata: Metadata - - /// Create a new single client request. - /// - /// - Parameters: - /// - message: The message to send to the server. - /// - metadata: Metadata to send to the client at the start of the response. Defaults to - /// empty. - /// - trailingMetadata: Metadata to send to the client at the end of the response. Defaults - /// to empty. - public init( - message: Message, - metadata: Metadata = [:], - trailingMetadata: Metadata = [:] - ) { - self.metadata = metadata - self.message = message - self.trailingMetadata = trailingMetadata - } - } - - /// Whether the RPC was accepted or rejected. - /// - /// The `success` indicates the server accepted the RPC for processing and the RPC completed - /// successfully and implies the RPC succeeded with the ``Status/Code-swift.struct/ok`` status - /// code. The `failure` case indicates that the service rejected the RPC without processing it - /// or could not process it successfully. - public var accepted: Result - - /// Creates a response. - /// - /// - Parameter accepted: Whether the RPC was accepted or rejected. - public init(accepted: Result) { - self.accepted = accepted - } - } -} - -extension ServerResponse { - /// A response for a stream of messages sent by a server. - /// - /// Stream responses are used for server-streaming and bidirectional-streaming RPCs. For single - /// responses see ``ServerResponse/Single``. - /// - /// A stream response captures every part of the response stream and distinguishes whether the - /// request was processed by the server via the ``accepted`` property. The value for the `success` - /// case contains the initial metadata and a closure which is provided with a message write and - /// returns trailing metadata. If the closure returns without error then the response implicitly - /// has an ``Status/Code-swift.struct/ok`` status code. You can throw an error from the producer - /// to indicate that the request couldn't be handled successfully. If an ``RPCError`` is thrown - /// then the client will receive an equivalent error populated with the same code and message. If - /// an error of any other type is thrown then the client will receive an error with the - /// ``Status/Code-swift.struct/unknown`` status code. - /// - /// The `failure` case indicates that the server chose not to process the RPC. The failure case - /// contains an ``RPCError`` describing why the RPC failed, including an error code, error - /// message and any metadata to send to the client. - /// - /// ### Using ``Stream`` responses - /// - /// Each response has an ``accepted`` property which contains all RPC information. You can create - /// one by calling ``init(accepted:)`` or one of the two convenience initializers: - /// - ``init(of:metadata:producer:)`` to create a successful response, or - /// - ``init(of:error:)`` to create a failed response. - /// - /// You can interrogate a response by inspecting the ``accepted`` property directly. The following - /// example demonstrates how you can use the API: - /// - /// ```swift - /// // Create a successful response - /// let response = ServerResponse.Stream( - /// of: String.self, - /// metadata: ["hello": "initial metadata"] - /// ) { writer in - /// // Write a few messages. - /// try await writer.write("Hello") - /// try await writer.write("World") - /// - /// // Send trailing metadata to the client. - /// return ["goodbye": "trailing metadata"] - /// } - /// ``` - @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) - public struct Stream: Sendable { - /// The contents of a response to a request which has been accepted for processing. - public struct Contents: Sendable { - /// Metadata to send to the client at the beginning of the response stream. - public var metadata: Metadata - - /// A closure which, when called, writes values into the provided writer and returns trailing - /// metadata indicating the end of the response stream. - /// - /// Returning metadata indicates a successful response and gRPC will terminate the RPC with - /// an ``Status/Code-swift.struct/ok`` status code. Throwing an error will terminate the RPC - /// with an appropriate status code. You can control the status code, message and metadata - /// returned to the client by throwing an ``RPCError``. If the error thrown is a type other - /// than ``RPCError`` then a status with code ``Status/Code-swift.struct/unknown`` will - /// be returned to the client. - /// - /// gRPC will invoke this function at most once therefore it isn't required to be idempotent. - public var producer: @Sendable (RPCWriter) async throws -> Metadata - - /// Create a ``Contents``. - /// - /// - Parameters: - /// - metadata: Metadata to send to the client at the start of the response. - /// - producer: A function which produces values - public init( - metadata: Metadata, - producer: @escaping @Sendable (RPCWriter) async throws -> Metadata - ) { - self.metadata = metadata - self.producer = producer - } - } - - /// Whether the RPC was accepted or rejected. - /// - /// The `success` case indicates that the service accepted the RPC for processing and will - /// send initial metadata back to the client before producing response messages. The RPC may - /// still result in failure by later throwing an error. - /// - /// The `failure` case indicates that the server rejected the RPC and will not process it. Only - /// the status and trailing metadata will be sent to the client. - public var accepted: Result - - /// Creates a response. - /// - /// - Parameter accepted: Whether the RPC was accepted or rejected. - public init(accepted: Result) { - self.accepted = accepted - } - } -} - -extension ServerResponse.Single { - /// Creates a new accepted response. - /// - /// - Parameters: - /// - metadata: Metadata to send to the client at the beginning of the response. - /// - message: The response message to send to the client. - /// - trailingMetadata: Metadata to send to the client at the end of the response. - public init(message: Message, metadata: Metadata = [:], trailingMetadata: Metadata = [:]) { - let contents = Contents( - message: message, - metadata: metadata, - trailingMetadata: trailingMetadata - ) - self.accepted = .success(contents) - } - - /// Creates a new failed response. - /// - /// - Parameters: - /// - messageType: The type of message. - /// - error: An error describing why the RPC failed. - public init(of messageType: Message.Type = Message.self, error: RPCError) { - self.accepted = .failure(error) - } - - /// Returns the metadata to be sent to the client at the start of the response. - /// - /// For rejected RPCs (in other words, where ``accepted`` is `failure`) the metadata is empty. - public var metadata: Metadata { - switch self.accepted { - case let .success(contents): - return contents.metadata - case .failure: - return [:] - } - } - - /// Returns the message to send to the client. - /// - /// - Throws: ``RPCError`` if the request failed. - public var message: Message { - get throws { - try self.accepted.map { $0.message }.get() - } - } - - /// Returns metadata to be sent to the client at the end of the response. - /// - /// Unlike ``metadata``, for rejected RPCs the metadata returned may contain values. - public var trailingMetadata: Metadata { - switch self.accepted { - case let .success(contents): - return contents.trailingMetadata - case let .failure(error): - return error.metadata - } - } -} - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -extension ServerResponse.Stream { - /// Creates a new accepted response. - /// - /// - Parameters: - /// - messageType: The type of message. - /// - metadata: Metadata to send to the client at the beginning of the response. - /// - producer: A closure which, when called, writes messages to the client. - public init( - of messageType: Message.Type = Message.self, - metadata: Metadata = [:], - producer: @escaping @Sendable (RPCWriter) async throws -> Metadata - ) { - let contents = Contents(metadata: metadata, producer: producer) - self.accepted = .success(contents) - } - - /// Creates a new failed response. - /// - /// - Parameters: - /// - messageType: The type of message. - /// - error: An error describing why the RPC failed. - public init(of messageType: Message.Type = Message.self, error: RPCError) { - self.accepted = .failure(error) - } - - /// Returns metadata received from the server at the start of the response. - /// - /// For rejected RPCs (in other words, where ``accepted`` is `failure`) the metadata is empty. - public var metadata: Metadata { - switch self.accepted { - case let .success(contents): - return contents.metadata - case .failure: - return [:] - } - } -} - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -extension ServerResponse.Stream { - public init(single response: ServerResponse.Single) { - switch response.accepted { - case .success(let contents): - let contents = Contents(metadata: contents.metadata) { - try await $0.write(contents.message) - return contents.trailingMetadata - } - self.accepted = .success(contents) - - case .failure(let error): - self.accepted = .failure(error) - } - } -} diff --git a/Sources/GRPCCore/Coding/Coding.swift b/Sources/GRPCCore/Coding/Coding.swift deleted file mode 100644 index 29569d3c0..000000000 --- a/Sources/GRPCCore/Coding/Coding.swift +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -/// Serializes a message into a sequence of bytes. -/// -/// Message serializers convert an input message to a sequence of bytes. Serializers are used to -/// convert messages into a form which is suitable for sending over a network. The reverse -/// operation, deserialization, is performed by a ``MessageDeserializer``. -/// -/// Serializers are used frequently and implementations should take care to ensure that -/// serialization is as cheap as possible. -public protocol MessageSerializer: Sendable { - /// The type of message this serializer can serialize. - associatedtype Message - - /// Serializes a ``Message`` into a sequence of bytes. - /// - /// - Parameter message: The message to serialize. - /// - Returns: The serialized bytes of a message. - func serialize(_ message: Message) throws -> [UInt8] -} - -/// Deserializes a sequence of bytes into a message. -/// -/// Message deserializers convert a sequence of bytes into a message. Deserializers are used to -/// convert bytes received from the network into an application specific message. The reverse -/// operation, serialization, is performed by a ``MessageSerializer``. -/// -/// Deserializers are used frequently and implementations should take care to ensure that -/// deserialization is as cheap as possible. -public protocol MessageDeserializer: Sendable { - /// The type of message this deserializer can deserialize. - associatedtype Message - - /// Deserializes a sequence of bytes into a ``Message``. - /// - /// - Parameter serializedMessageBytes: The bytes to deserialize. - /// - Returns: The deserialized message. - func deserialize(_ serializedMessageBytes: [UInt8]) throws -> Message -} diff --git a/Sources/GRPCCore/Coding/CompressionAlgorithm.swift b/Sources/GRPCCore/Coding/CompressionAlgorithm.swift deleted file mode 100644 index 7b54e6636..000000000 --- a/Sources/GRPCCore/Coding/CompressionAlgorithm.swift +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -/// Message compression algorithms. -public struct CompressionAlgorithm: Hashable, Sendable { - package enum Value: UInt8, Hashable, Sendable, CaseIterable { - case none = 0 - case deflate - case gzip - } - - package let value: Value - - fileprivate init(_ algorithm: Value) { - self.value = algorithm - } - - /// No compression, sometimes referred to as 'identity' compression. - public static var none: Self { - Self(.none) - } - - /// The 'deflate' compression algorithm. - public static var deflate: Self { - Self(.deflate) - } - - /// The 'gzip' compression algorithm. - public static var gzip: Self { - Self(.gzip) - } -} - -/// A set of compression algorithms. -public struct CompressionAlgorithmSet: OptionSet, Hashable, Sendable { - public var rawValue: UInt32 - - public init(rawValue: UInt32) { - self.rawValue = rawValue - } - - private init(value: CompressionAlgorithm.Value) { - self.rawValue = 1 << value.rawValue - } - - /// No compression, sometimes referred to as 'identity' compression. - public static var none: Self { - return Self(value: .none) - } - - /// The 'deflate' compression algorithm. - public static var deflate: Self { - return Self(value: .deflate) - } - - /// The 'gzip' compression algorithm. - public static var gzip: Self { - return Self(value: .gzip) - } - - /// All compression algorithms. - public static var all: Self { - return [.gzip, .deflate, .none] - } - - /// Returns whether a given algorithm is present in the set. - /// - /// - Parameter algorithm: The algorithm to check. - public func contains(_ algorithm: CompressionAlgorithm) -> Bool { - return self.contains(CompressionAlgorithmSet(value: algorithm.value)) - } -} - -extension CompressionAlgorithmSet { - /// A sequence of ``CompressionAlgorithm`` values present in the set. - public var elements: Elements { - Elements(algorithmSet: self) - } - - /// A sequence of ``CompressionAlgorithm`` values present in a ``CompressionAlgorithmSet``. - public struct Elements: Sequence { - public typealias Element = CompressionAlgorithm - - private let algorithmSet: CompressionAlgorithmSet - - init(algorithmSet: CompressionAlgorithmSet) { - self.algorithmSet = algorithmSet - } - - public func makeIterator() -> Iterator { - return Iterator(algorithmSet: self.algorithmSet) - } - - public struct Iterator: IteratorProtocol { - private let algorithmSet: CompressionAlgorithmSet - private var iterator: IndexingIterator<[CompressionAlgorithm.Value]> - - init(algorithmSet: CompressionAlgorithmSet) { - self.algorithmSet = algorithmSet - self.iterator = CompressionAlgorithm.Value.allCases.makeIterator() - } - - public mutating func next() -> CompressionAlgorithm? { - while let value = self.iterator.next() { - if self.algorithmSet.contains(CompressionAlgorithmSet(value: value)) { - return CompressionAlgorithm(value) - } - } - - return nil - } - } - } -} diff --git a/Sources/GRPCCore/Configuration/MethodConfig.swift b/Sources/GRPCCore/Configuration/MethodConfig.swift deleted file mode 100644 index 295d049a3..000000000 --- a/Sources/GRPCCore/Configuration/MethodConfig.swift +++ /dev/null @@ -1,731 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -/// Configuration values for executing an RPC. -/// -/// See also: https://github.com/grpc/grpc-proto/blob/master/grpc/service_config/service_config.proto -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -public struct MethodConfig: Hashable, Sendable { - public struct Name: Sendable, Hashable { - /// The name of the service, including the namespace. - /// - /// If the service is empty then `method` must also be empty and the configuration specifies - /// defaults for all methods. - /// - /// - Precondition: If `service` is empty then `method` must also be empty. - public var service: String { - didSet { try! self.validate() } - } - - /// The name of the method. - /// - /// If the method is empty then the configuration will be the default for all methods in the - /// specified service. - public var method: String - - /// Create a new name. - /// - /// If the service is empty then `method` must also be empty and the configuration specifies - /// defaults for all methods. If only `method` is empty then the configuration applies to - /// all methods in the `service`. - /// - /// - Parameters: - /// - service: The name of the service, including the namespace. - /// - method: The name of the method. - public init(service: String, method: String = "") { - self.service = service - self.method = method - try! self.validate() - } - - private func validate() throws { - if self.service.isEmpty && !self.method.isEmpty { - throw RuntimeError( - code: .invalidArgument, - message: "'method' must be empty if 'service' is empty." - ) - } - } - } - - /// The names of methods which this configuration applies to. - public var names: [Name] - - /// Whether RPCs for this method should wait until the connection is ready. - /// - /// If `false` the RPC will abort immediately if there is a transient failure connecting to - /// the server. Otherwise gRPC will attempt to connect until the deadline is exceeded. - public var waitForReady: Bool? - - /// The default timeout for the RPC. - /// - /// If no reply is received in the specified amount of time the request is aborted - /// with an ``RPCError`` with code ``RPCError/Code/deadlineExceeded``. - /// - /// The actual deadline used will be the minimum of the value specified here - /// and the value set by the application by the client API. If either one isn't set - /// then the other value is used. If neither is set then the request has no deadline. - /// - /// The timeout applies to the overall execution of an RPC. If, for example, a retry - /// policy is set then the timeout begins when the first attempt is started and _isn't_ reset - /// when subsequent attempts start. - public var timeout: Duration? - - /// The maximum allowed payload size in bytes for an individual message. - /// - /// If a client attempts to send an object larger than this value, it will not be sent and the - /// client will see an error. Note that 0 is a valid value, meaning that the request message - /// must be empty. - /// - /// Note that if compression is used the uncompressed message size is validated. - public var maxRequestMessageBytes: Int? - - /// The maximum allowed payload size in bytes for an individual response message. - /// - /// If a server attempts to send an object larger than this value, it will not - /// be sent, and an error will be sent to the client instead. Note that 0 is a valid value, - /// meaning that the response message must be empty. - /// - /// Note that if compression is used the uncompressed message size is validated. - public var maxResponseMessageBytes: Int? - - /// The policy determining how many times, and when, the RPC is executed. - /// - /// There are two policy types: - /// 1. Retry - /// 2. Hedging - /// - /// The retry policy allows an RPC to be retried a limited number of times if the RPC - /// fails with one of the configured set of status codes. RPCs are only retried if they - /// fail immediately, that is, the first response part received from the server is a - /// status code. - /// - /// The hedging policy allows an RPC to be executed multiple times concurrently. Typically - /// each execution will be staggered by some delay. The first successful response will be - /// reported to the client. Hedging is only suitable for idempotent RPCs. - public var executionPolicy: RPCExecutionPolicy? - - /// Create an execution configuration. - /// - /// - Parameters: - /// - names: The names of methods this configuration applies to. - /// - waitForReady: Whether RPCs sent to this method should wait until the connection is ready. - /// - timeout: The default timeout for the RPC. - /// - maxRequestMessageBytes: The maximum allowed size of a request message in bytes. - /// - maxResponseMessageBytes: The maximum allowed size of a response message in bytes. - /// - executionPolicy: The execution policy to use for the RPC. - public init( - names: [Name], - waitForReady: Bool? = nil, - timeout: Duration? = nil, - maxRequestMessageBytes: Int? = nil, - maxResponseMessageBytes: Int? = nil, - executionPolicy: RPCExecutionPolicy? = nil - ) { - self.names = names - self.waitForReady = waitForReady - self.timeout = timeout - self.maxRequestMessageBytes = maxRequestMessageBytes - self.maxResponseMessageBytes = maxResponseMessageBytes - self.executionPolicy = executionPolicy - } -} - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -public struct RPCExecutionPolicy: Hashable, Sendable { - @usableFromInline - enum Wrapped: Hashable, Sendable { - /// Policy for retrying an RPC. - /// - /// See ``RetryPolicy`` for more details. - case retry(RetryPolicy) - - /// Policy for hedging an RPC. - /// - /// See ``HedgingPolicy`` for more details. - case hedge(HedgingPolicy) - } - - @usableFromInline - let wrapped: Wrapped - - private init(_ wrapped: Wrapped) { - self.wrapped = wrapped - } - - /// Returns the retry policy, if it was set. - public var retry: RetryPolicy? { - switch self.wrapped { - case .retry(let policy): - return policy - case .hedge: - return nil - } - } - - /// Returns the hedging policy, if it was set. - public var hedge: HedgingPolicy? { - switch self.wrapped { - case .hedge(let policy): - return policy - case .retry: - return nil - } - } - - /// Create a new retry policy.`` - public static func retry(_ policy: RetryPolicy) -> Self { - Self(.retry(policy)) - } - - /// Create a new hedging policy.`` - public static func hedge(_ policy: HedgingPolicy) -> Self { - Self(.hedge(policy)) - } -} - -/// Policy for retrying an RPC. -/// -/// gRPC retries RPCs when the first response from the server is a status code which matches -/// one of the configured retryable status codes. If the server begins processing the RPC and -/// first responds with metadata and later responds with a retryable status code then the RPC -/// won't be retried. -/// -/// Execution attempts are limited by ``maxAttempts`` which includes the original attempt. The -/// maximum number of attempts is limited to five. -/// -/// Subsequent attempts are executed after some delay. The first _retry_, or second attempt, will -/// be started after a randomly chosen delay between zero and ``initialBackoff``. More generally, -/// the nth retry will happen after a randomly chosen delay between zero -/// and `min(initialBackoff * backoffMultiplier^(n-1), maxBackoff)`. -/// -/// For more information see [gRFC A6 Client -/// Retries](https://github.com/grpc/proposal/blob/master/A6-client-retries.md). -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -public struct RetryPolicy: Hashable, Sendable { - /// The maximum number of RPC attempts, including the original attempt. - /// - /// Must be greater than one, values greater than five are treated as five. - public var maxAttempts: Int { - didSet { self.maxAttempts = try! validateMaxAttempts(self.maxAttempts) } - } - - /// The initial backoff duration. - /// - /// The initial retry will occur after a random amount of time up to this value. - /// - /// - Precondition: Must be greater than zero. - public var initialBackoff: Duration { - willSet { try! Self.validateInitialBackoff(newValue) } - } - - /// The maximum amount of time to backoff for. - /// - /// - Precondition: Must be greater than zero. - public var maxBackoff: Duration { - willSet { try! Self.validateMaxBackoff(newValue) } - } - - /// The multiplier to apply to backoff. - /// - /// - Precondition: Must be greater than zero. - public var backoffMultiplier: Double { - willSet { try! Self.validateBackoffMultiplier(newValue) } - } - - /// The set of status codes which may be retried. - /// - /// - Precondition: Must not be empty. - public var retryableStatusCodes: Set { - willSet { try! Self.validateRetryableStatusCodes(newValue) } - } - - /// Create a new retry policy. - /// - /// - Parameters: - /// - maxAttempts: The maximum number of attempts allowed for the RPC. - /// - initialBackoff: The initial backoff period for the first retry attempt. Must be - /// greater than zero. - /// - maxBackoff: The maximum period of time to wait between attempts. Must be greater than - /// zero. - /// - backoffMultiplier: The exponential backoff multiplier. Must be greater than zero. - /// - retryableStatusCodes: The set of status codes which may be retried. Must not be empty. - /// - Precondition: `maxAttempts`, `initialBackoff`, `maxBackoff` and `backoffMultiplier` - /// must be greater than zero. - /// - Precondition: `retryableStatusCodes` must not be empty. - public init( - maxAttempts: Int, - initialBackoff: Duration, - maxBackoff: Duration, - backoffMultiplier: Double, - retryableStatusCodes: Set - ) { - self.maxAttempts = try! validateMaxAttempts(maxAttempts) - - try! Self.validateInitialBackoff(initialBackoff) - self.initialBackoff = initialBackoff - - try! Self.validateMaxBackoff(maxBackoff) - self.maxBackoff = maxBackoff - - try! Self.validateBackoffMultiplier(backoffMultiplier) - self.backoffMultiplier = backoffMultiplier - - try! Self.validateRetryableStatusCodes(retryableStatusCodes) - self.retryableStatusCodes = retryableStatusCodes - } - - private static func validateInitialBackoff(_ value: Duration) throws { - if value <= .zero { - throw RuntimeError( - code: .invalidArgument, - message: "initialBackoff must be greater than zero" - ) - } - } - - private static func validateMaxBackoff(_ value: Duration) throws { - if value <= .zero { - throw RuntimeError( - code: .invalidArgument, - message: "maxBackoff must be greater than zero" - ) - } - } - - private static func validateBackoffMultiplier(_ value: Double) throws { - if value <= 0 { - throw RuntimeError( - code: .invalidArgument, - message: "backoffMultiplier must be greater than zero" - ) - } - } - - private static func validateRetryableStatusCodes(_ value: Set) throws { - if value.isEmpty { - throw RuntimeError(code: .invalidArgument, message: "retryableStatusCodes mustn't be empty") - } - } -} - -/// Policy for hedging an RPC. -/// -/// Hedged RPCs may execute more than once on a server so only idempotent methods should -/// be hedged. -/// -/// gRPC executes the RPC at most ``maxAttempts`` times, staggering each attempt -/// by ``hedgingDelay``. -/// -/// For more information see [gRFC A6 Client -/// Retries](https://github.com/grpc/proposal/blob/master/A6-client-retries.md). -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -public struct HedgingPolicy: Hashable, Sendable { - /// The maximum number of RPC attempts, including the original attempt. - /// - /// Values greater than five are treated as five. - /// - /// - Precondition: Must be greater than one. - public var maxAttempts: Int { - didSet { self.maxAttempts = try! validateMaxAttempts(self.maxAttempts) } - } - - /// The first RPC will be sent immediately, but each subsequent RPC will be sent at intervals - /// of `hedgingDelay`. Set this to zero to immediately send all RPCs. - public var hedgingDelay: Duration { - willSet { try! Self.validateHedgingDelay(newValue) } - } - - /// The set of status codes which indicate other hedged RPCs may still succeed. - /// - /// If a non-fatal status code is returned by the server, hedged RPCs will continue. - /// Otherwise, outstanding requests will be cancelled and the error returned to the - /// application layer. - public var nonFatalStatusCodes: Set - - /// Create a new hedging policy. - /// - /// - Parameters: - /// - maxAttempts: The maximum number of attempts allowed for the RPC. - /// - hedgingDelay: The delay between each hedged RPC. - /// - nonFatalStatusCodes: The set of status codes which indicate other hedged RPCs may still - /// succeed. - /// - Precondition: `maxAttempts` must be greater than zero. - public init( - maxAttempts: Int, - hedgingDelay: Duration, - nonFatalStatusCodes: Set - ) { - self.maxAttempts = try! validateMaxAttempts(maxAttempts) - - try! Self.validateHedgingDelay(hedgingDelay) - self.hedgingDelay = hedgingDelay - self.nonFatalStatusCodes = nonFatalStatusCodes - } - - private static func validateHedgingDelay(_ value: Duration) throws { - if value < .zero { - throw RuntimeError( - code: .invalidArgument, - message: "hedgingDelay must be greater than or equal to zero" - ) - } - } -} - -private func validateMaxAttempts(_ value: Int) throws -> Int { - guard value > 1 else { - throw RuntimeError( - code: .invalidArgument, - message: "max_attempts must be greater than one (was \(value))" - ) - } - - return min(value, 5) -} - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -extension Duration { - fileprivate init(googleProtobufDuration duration: String) throws { - guard duration.utf8.last == UInt8(ascii: "s"), - let fractionalSeconds = Double(duration.dropLast()) - else { - throw RuntimeError(code: .invalidArgument, message: "Invalid google.protobuf.duration") - } - - let seconds = fractionalSeconds.rounded(.down) - let attoseconds = (fractionalSeconds - seconds) / 1e18 - - self.init(secondsComponent: Int64(seconds), attosecondsComponent: Int64(attoseconds)) - } -} - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -extension MethodConfig: Codable { - private enum CodingKeys: String, CodingKey { - case name - case waitForReady - case timeout - case maxRequestMessageBytes - case maxResponseMessageBytes - case retryPolicy - case hedgingPolicy - } - - public init(from decoder: any Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.names = try container.decode([Name].self, forKey: .name) - - let waitForReady = try container.decodeIfPresent(Bool.self, forKey: .waitForReady) - self.waitForReady = waitForReady - - let timeout = try container.decodeIfPresent(GoogleProtobufDuration.self, forKey: .timeout) - self.timeout = timeout?.duration - - let maxRequestSize = try container.decodeIfPresent(Int.self, forKey: .maxRequestMessageBytes) - self.maxRequestMessageBytes = maxRequestSize - - let maxResponseSize = try container.decodeIfPresent(Int.self, forKey: .maxResponseMessageBytes) - self.maxResponseMessageBytes = maxResponseSize - - if let policy = try container.decodeIfPresent(HedgingPolicy.self, forKey: .hedgingPolicy) { - self.executionPolicy = .hedge(policy) - } else if let policy = try container.decodeIfPresent(RetryPolicy.self, forKey: .retryPolicy) { - self.executionPolicy = .retry(policy) - } else { - self.executionPolicy = nil - } - } - - public func encode(to encoder: any Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(self.names, forKey: .name) - try container.encodeIfPresent(self.waitForReady, forKey: .waitForReady) - try container.encodeIfPresent( - self.timeout.map { GoogleProtobufDuration(duration: $0) }, - forKey: .timeout - ) - try container.encodeIfPresent(self.maxRequestMessageBytes, forKey: .maxRequestMessageBytes) - try container.encodeIfPresent(self.maxResponseMessageBytes, forKey: .maxResponseMessageBytes) - - switch self.executionPolicy?.wrapped { - case .retry(let policy): - try container.encode(policy, forKey: .retryPolicy) - case .hedge(let policy): - try container.encode(policy, forKey: .hedgingPolicy) - case .none: - () - } - } -} - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -extension MethodConfig.Name: Codable { - private enum CodingKeys: String, CodingKey { - case service - case method - } - - public init(from decoder: any Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - let service = try container.decodeIfPresent(String.self, forKey: .service) - self.service = service ?? "" - - let method = try container.decodeIfPresent(String.self, forKey: .method) - self.method = method ?? "" - - try self.validate() - } - - public func encode(to encoder: any Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(self.method, forKey: .method) - try container.encode(self.service, forKey: .service) - } -} - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -extension RetryPolicy: Codable { - private enum CodingKeys: String, CodingKey { - case maxAttempts - case initialBackoff - case maxBackoff - case backoffMultiplier - case retryableStatusCodes - } - - public init(from decoder: any Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - let maxAttempts = try container.decode(Int.self, forKey: .maxAttempts) - self.maxAttempts = try validateMaxAttempts(maxAttempts) - - let initialBackoff = try container.decode(String.self, forKey: .initialBackoff) - self.initialBackoff = try Duration(googleProtobufDuration: initialBackoff) - try Self.validateInitialBackoff(self.initialBackoff) - - let maxBackoff = try container.decode(String.self, forKey: .maxBackoff) - self.maxBackoff = try Duration(googleProtobufDuration: maxBackoff) - try Self.validateMaxBackoff(self.maxBackoff) - - self.backoffMultiplier = try container.decode(Double.self, forKey: .backoffMultiplier) - try Self.validateBackoffMultiplier(self.backoffMultiplier) - - let codes = try container.decode([GoogleRPCCode].self, forKey: .retryableStatusCodes) - self.retryableStatusCodes = Set(codes.map { $0.code }) - try Self.validateRetryableStatusCodes(self.retryableStatusCodes) - } - - public func encode(to encoder: any Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(self.maxAttempts, forKey: .maxAttempts) - try container.encode( - GoogleProtobufDuration(duration: self.initialBackoff), - forKey: .initialBackoff - ) - try container.encode(GoogleProtobufDuration(duration: self.maxBackoff), forKey: .maxBackoff) - try container.encode(self.backoffMultiplier, forKey: .backoffMultiplier) - try container.encode( - self.retryableStatusCodes.map { $0.googleRPCCode }, - forKey: .retryableStatusCodes - ) - } -} - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -extension HedgingPolicy: Codable { - private enum CodingKeys: String, CodingKey { - case maxAttempts - case hedgingDelay - case nonFatalStatusCodes - } - - public init(from decoder: any Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - let maxAttempts = try container.decode(Int.self, forKey: .maxAttempts) - self.maxAttempts = try validateMaxAttempts(maxAttempts) - - let delay = try container.decode(String.self, forKey: .hedgingDelay) - self.hedgingDelay = try Duration(googleProtobufDuration: delay) - - let statusCodes = try container.decode([GoogleRPCCode].self, forKey: .nonFatalStatusCodes) - self.nonFatalStatusCodes = Set(statusCodes.map { $0.code }) - } - - public func encode(to encoder: any Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(self.maxAttempts, forKey: .maxAttempts) - try container.encode(GoogleProtobufDuration(duration: self.hedgingDelay), forKey: .hedgingDelay) - try container.encode( - self.nonFatalStatusCodes.map { $0.googleRPCCode }, - forKey: .nonFatalStatusCodes - ) - } -} - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -struct GoogleProtobufDuration: Codable { - var duration: Duration - - init(duration: Duration) { - self.duration = duration - } - - init(from decoder: any Decoder) throws { - let container = try decoder.singleValueContainer() - let duration = try container.decode(String.self) - - guard duration.utf8.last == UInt8(ascii: "s"), - let fractionalSeconds = Double(duration.dropLast()) - else { - throw RuntimeError(code: .invalidArgument, message: "Invalid google.protobuf.duration") - } - - let seconds = fractionalSeconds.rounded(.down) - let attoseconds = (fractionalSeconds - seconds) * 1e18 - - self.duration = Duration( - secondsComponent: Int64(seconds), - attosecondsComponent: Int64(attoseconds) - ) - } - - func encode(to encoder: any Encoder) throws { - var container = encoder.singleValueContainer() - - var seconds = Double(self.duration.components.seconds) - seconds += Double(self.duration.components.attoseconds) / 1e18 - - let durationString = "\(seconds)s" - try container.encode(durationString) - } -} - -struct GoogleRPCCode: Codable { - var code: Status.Code - - init(code: Status.Code) { - self.code = code - } - - init(from decoder: any Decoder) throws { - let container = try decoder.singleValueContainer() - let code: Status.Code? - - if let caseName = try? container.decode(String.self) { - code = Status.Code(googleRPCCode: caseName) - } else if let rawValue = try? container.decode(Int.self) { - code = Status.Code(rawValue: rawValue) - } else { - code = nil - } - - if let code = code { - self.code = code - } else { - throw RuntimeError(code: .invalidArgument, message: "Invalid google.rpc.code") - } - } - - func encode(to encoder: any Encoder) throws { - var container = encoder.singleValueContainer() - try container.encode(self.code.googleRPCCode) - } -} - -extension Status.Code { - fileprivate init?(googleRPCCode code: String) { - switch code { - case "OK": - self = .ok - case "CANCELLED": - self = .cancelled - case "UNKNOWN": - self = .unknown - case "INVALID_ARGUMENT": - self = .invalidArgument - case "DEADLINE_EXCEEDED": - self = .deadlineExceeded - case "NOT_FOUND": - self = .notFound - case "ALREADY_EXISTS": - self = .alreadyExists - case "PERMISSION_DENIED": - self = .permissionDenied - case "RESOURCE_EXHAUSTED": - self = .resourceExhausted - case "FAILED_PRECONDITION": - self = .failedPrecondition - case "ABORTED": - self = .aborted - case "OUT_OF_RANGE": - self = .outOfRange - case "UNIMPLEMENTED": - self = .unimplemented - case "INTERNAL": - self = .internalError - case "UNAVAILABLE": - self = .unavailable - case "DATA_LOSS": - self = .dataLoss - case "UNAUTHENTICATED": - self = .unauthenticated - default: - return nil - } - } - - fileprivate var googleRPCCode: String { - switch self.wrapped { - case .ok: - return "OK" - case .cancelled: - return "CANCELLED" - case .unknown: - return "UNKNOWN" - case .invalidArgument: - return "INVALID_ARGUMENT" - case .deadlineExceeded: - return "DEADLINE_EXCEEDED" - case .notFound: - return "NOT_FOUND" - case .alreadyExists: - return "ALREADY_EXISTS" - case .permissionDenied: - return "PERMISSION_DENIED" - case .resourceExhausted: - return "RESOURCE_EXHAUSTED" - case .failedPrecondition: - return "FAILED_PRECONDITION" - case .aborted: - return "ABORTED" - case .outOfRange: - return "OUT_OF_RANGE" - case .unimplemented: - return "UNIMPLEMENTED" - case .internalError: - return "INTERNAL" - case .unavailable: - return "UNAVAILABLE" - case .dataLoss: - return "DATA_LOSS" - case .unauthenticated: - return "UNAUTHENTICATED" - } - } -} diff --git a/Sources/GRPCCore/Configuration/ServiceConfig.swift b/Sources/GRPCCore/Configuration/ServiceConfig.swift deleted file mode 100644 index f0e41c4bc..000000000 --- a/Sources/GRPCCore/Configuration/ServiceConfig.swift +++ /dev/null @@ -1,279 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -/// Service configuration values. -/// -/// See also: https://github.com/grpc/grpc-proto/blob/master/grpc/service_config/service_config.proto -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -public struct ServiceConfig: Hashable, Sendable { - /// Per-method configuration. - public var methodConfig: [MethodConfig] - - /// Load balancing policies. - /// - /// The client iterates through the list in order and picks the first configuration it supports. - /// If no policies are supported then the configuration is considered to be invalid. - public var loadBalancingConfig: [LoadBalancingConfig] - - /// The policy for throttling retries. - /// - /// If ``RetryThrottling`` is provided, gRPC will automatically throttle retry attempts - /// and hedged RPCs when the client's ratio of failures to successes exceeds a threshold. - /// - /// For each server name, the gRPC client will maintain a `token_count` which is initially set - /// to ``RetryThrottling-swift.struct/maxTokens``. Every outgoing RPC (regardless of service or - /// method invoked) will change `token_count` as follows: - /// - /// - Every failed RPC will decrement the `token_count` by 1. - /// - Every successful RPC will increment the `token_count` by - /// ``RetryThrottling-swift.struct/tokenRatio``. - /// - /// If `token_count` is less than or equal to `max_tokens / 2`, then RPCs will not be retried - /// and hedged RPCs will not be sent. - public var retryThrottling: RetryThrottling? - - /// Creates a new ``ServiceConfig``. - /// - /// - Parameters: - /// - methodConfig: Per-method configuration. - /// - loadBalancingConfig: Load balancing policies. Clients use the the first supported - /// policy when iterating the list in order. - /// - retryThrottling: Policy for throttling retries. - public init( - methodConfig: [MethodConfig] = [], - loadBalancingConfig: [LoadBalancingConfig] = [], - retryThrottling: RetryThrottling? = nil - ) { - self.methodConfig = methodConfig - self.loadBalancingConfig = loadBalancingConfig - self.retryThrottling = retryThrottling - } -} - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -extension ServiceConfig: Codable { - private enum CodingKeys: String, CodingKey { - case methodConfig - case loadBalancingConfig - case retryThrottling - } - - public init(from decoder: any Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - let methodConfig = try container.decodeIfPresent( - [MethodConfig].self, - forKey: .methodConfig - ) - self.methodConfig = methodConfig ?? [] - - let loadBalancingConfiguration = try container.decodeIfPresent( - [LoadBalancingConfig].self, - forKey: .loadBalancingConfig - ) - self.loadBalancingConfig = loadBalancingConfiguration ?? [] - - self.retryThrottling = try container.decodeIfPresent( - RetryThrottling.self, - forKey: .retryThrottling - ) - } - - public func encode(to encoder: any Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(self.methodConfig, forKey: .methodConfig) - try container.encode(self.loadBalancingConfig, forKey: .loadBalancingConfig) - try container.encodeIfPresent(self.retryThrottling, forKey: .retryThrottling) - } -} - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -extension ServiceConfig { - /// Configuration used by clients for load-balancing. - public struct LoadBalancingConfig: Hashable, Sendable { - private enum Value: Hashable, Sendable { - case pickFirst(PickFirst) - case roundRobin(RoundRobin) - } - - private var value: Value? - private init(_ value: Value) { - self.value = value - } - - /// Creates a pick-first load balancing policy. - /// - /// - Parameter shuffleAddressList: Whether resolved addresses should be shuffled before - /// attempting to connect to them. - public static func pickFirst(shuffleAddressList: Bool) -> Self { - Self(.pickFirst(PickFirst(shuffleAddressList: shuffleAddressList))) - } - - /// Creates a pick-first load balancing policy. - /// - /// - Parameter pickFirst: The pick-first load balancing policy. - public static func pickFirst(_ pickFirst: PickFirst) -> Self { - Self(.pickFirst(pickFirst)) - } - - /// Creates a round-robin load balancing policy. - public static var roundRobin: Self { - Self(.roundRobin(RoundRobin())) - } - - /// The pick-first policy, if configured. - public var pickFirst: PickFirst? { - get { - switch self.value { - case .pickFirst(let value): - return value - default: - return nil - } - } - set { - self.value = newValue.map { .pickFirst($0) } - } - } - - /// The round-robin policy, if configured. - public var roundRobin: RoundRobin? { - get { - switch self.value { - case .roundRobin(let value): - return value - default: - return nil - } - } - set { - self.value = newValue.map { .roundRobin($0) } - } - } - } -} - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -extension ServiceConfig.LoadBalancingConfig { - /// Configuration for the pick-first load balancing policy. - public struct PickFirst: Hashable, Sendable, Codable { - /// Whether the resolved addresses should be shuffled before attempting to connect to them. - public var shuffleAddressList: Bool - - /// Creates a new pick-first load balancing policy. - /// - Parameter shuffleAddressList: Whether the resolved addresses should be shuffled before - /// attempting to connect to them. - public init(shuffleAddressList: Bool = false) { - self.shuffleAddressList = shuffleAddressList - } - - public init(from decoder: any Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - let shuffle = try container.decodeIfPresent(Bool.self, forKey: .shuffleAddressList) ?? false - self.shuffleAddressList = shuffle - } - } - - /// Configuration for the round-robin load balancing policy. - public struct RoundRobin: Hashable, Sendable, Codable { - /// Creates a new round-robin load balancing policy. - public init() {} - } -} - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -extension ServiceConfig.LoadBalancingConfig: Codable { - private enum CodingKeys: String, CodingKey { - case roundRobin = "round_robin" - case pickFirst = "pick_first" - } - - public init(from decoder: any Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - if let value = try container.decodeIfPresent(RoundRobin.self, forKey: .roundRobin) { - self.value = .roundRobin(value) - } else if let value = try container.decodeIfPresent(PickFirst.self, forKey: .pickFirst) { - self.value = .pickFirst(value) - } else { - self.value = nil - } - } - - public func encode(to encoder: any Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - switch self.value { - case .pickFirst(let value): - try container.encode(value, forKey: .pickFirst) - case .roundRobin(let value): - try container.encode(value, forKey: .roundRobin) - case .none: - () - } - } -} - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -extension ServiceConfig { - public struct RetryThrottling: Hashable, Sendable, Codable { - /// The initial, and maximum number of tokens. - /// - /// - Precondition: Must be greater than zero. - public var maxTokens: Int - - /// The amount of tokens to add on each successful RPC. - /// - /// Typically this will be some number between 0 and 1, e.g., 0.1. Up to three decimal places - /// are supported. - /// - /// - Precondition: Must be greater than zero. - public var tokenRatio: Double - - /// Creates a new retry throttling policy. - /// - /// - Parameters: - /// - maxTokens: The initial, and maximum number of tokens. Must be greater than zero. - /// - tokenRatio: The amount of tokens to add on each successful RPC. Must be greater - /// than zero. - public init(maxTokens: Int, tokenRatio: Double) throws { - self.maxTokens = maxTokens - self.tokenRatio = tokenRatio - - try self.validateMaxTokens() - try self.validateTokenRatio() - } - - public init(from decoder: any Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - self.maxTokens = try container.decode(Int.self, forKey: .maxTokens) - self.tokenRatio = try container.decode(Double.self, forKey: .tokenRatio) - - try self.validateMaxTokens() - try self.validateTokenRatio() - } - - private func validateMaxTokens() throws { - if self.maxTokens <= 0 { - throw RuntimeError(code: .invalidArgument, message: "maxTokens must be greater than zero") - } - } - - private func validateTokenRatio() throws { - if self.tokenRatio <= 0 { - throw RuntimeError(code: .invalidArgument, message: "tokenRatio must be greater than zero") - } - } - } -} diff --git a/Sources/GRPCCore/Documentation.docc/Articles/Generating-stubs.md b/Sources/GRPCCore/Documentation.docc/Articles/Generating-stubs.md deleted file mode 100644 index 34b57f8d2..000000000 --- a/Sources/GRPCCore/Documentation.docc/Articles/Generating-stubs.md +++ /dev/null @@ -1,204 +0,0 @@ -# Generating stubs - -Learn how to generate stubs for gRPC Swift from a service defined using the Protocol Buffers IDL. - -## Overview - -There are two approaches to generating stubs from Protocol Buffers: - -1. With the Swift Package Manager build plugin, or -2. With the Protocol Buffers compiler (`protoc`). - -The following sections describe how and when to use each. - -### Using the Swift Package Manager build plugin - -You can generate stubs at build time by using `GRPCSwiftPlugin` which is a build plugin for the -Swift Package Manager. Using it means that you don't have to manage the generation of -stubs with separate tooling, or check the generated stubs into your source repository. - -The build plugin will generate gRPC stubs for you by building `protoc-gen-grpc-swift` (more details -in the following section) for you and invoking `protoc`. Because of the implicit -dependency on `protoc` being made available by the system `GRPCSwiftPlugin` isn't suitable for use -in: - -- Library packages, or -- Environments where `protoc` isn't available. - -> `GRPCSwiftPlugin` _only_ generates gRPC stubs, it doesn't generate messages. You must generate -> messages in addition to the gRPC Stubs. The [Swift Protobuf](https://github.com/apple/swift-protobuf) -> project provides an equivalent build plugin, `SwiftProtobufPlugin`, for this. - -#### Configuring the build plugin - -You can configure which stubs `GRPCSwiftPlugin` generates and how via a configuration file. This -must be called `grpc-swift-config.json` and can be placed anywhere in the source directory for your -target. - -A config file for the plugin is made up of a number of `protoc` invocations. Each invocation -describes the inputs to `protoc` as well as any options. - -The following is a list of options which can be applied to each invocation object: -- `protoFiles`, an array of strings where each string is the path to an input `.proto` file - _relative to `grpc-swift-config.json`_. -- `visibility`, a string describing the access level of the generated stub (must be one - of `"public"`, `"internal"`, or `"package"`). If not specified then stubs are generated as - `internal`. -- `server`, a boolean indicating whether server stubs should be generated. Defaults to `true` if - not specified. -- `client`, a boolean indicating whether client stubs should be generated. Defaults to `true` if - not specified. -- `_V2`, a boolean indicated whether the generated stubs should be for v2.x. Defaults to `false` if - not specified. - -> The `GRPCSwiftPlugin` build plugin is currently shared between gRPC Swift v1.x and v2.x. To -> generate stubs for v2.x you _must_ set `_V2` to `true` in your config. -> -> This option will be deprecated and removed once v2.x has been released. - -#### Finding protoc - -The build plugin requires a copy of the `protoc` binary to be available. To resolve which copy of -the binary to use, `GRPCSwiftPlugin` will look at the following in order: - -1. The exact path specified in the `protocPath` property in `grpc-swift-config.json`, if present. -2. The exact path specified in the `PROTOC_PATH` environment variable, if set. -3. The first `protoc` binary found in your `PATH` environment variable. - -#### Using the build plugin from Xcode - -Xcode doesn't have access to your `PATH` so in order to use `GRPCSwiftPlugin` with Xcode you must -either set `protocPath` in your `grpc-swift-config.json` or explicitly set `PROTOC_PATH` when -opening Xcode. - -You can do this by running: - -```sh -env PROTOC_PATH=/path/to/protoc xed /path/to/your-project -``` - -Note that Xcode must _not_ be open before running this command. - -#### Example configuration - -We recommend putting your config and `.proto` files in a directory called `Protos` within your -target. Here's an example package structure: - -``` -MyPackage -├── Package.swift -└── Sources - └── MyTarget - └── Protos - ├── foo - │   └── bar - │   ├── baz.proto - │   └── buzz.proto - └── grpc-swift-config.json -``` - -If you wanted the generated stubs from `baz.proto` to be `public`, and to only generate a client -for `buzz.proto` then the `grpc-swift-config` could look like this: - -```json -{ - "invocations": [ - { - "_V2": true, - "protoFiles": ["foo/bar/baz.proto"], - "visibility": "public" - }, - { - "_V2": true, - "protoFiles": ["foo/bar/buzz.proto"], - "server": false - } - ] -} -``` - -### Using protoc - -If you've used Protocol Buffers before then generating gRPC Swift stubs should be simple. If you're -unfamiliar with Protocol Buffers then you should get comfortable with the concepts before -continuing; the [Protocol Buffers website](https://protobuf.dev/) is a great place to start. - -gRPC Swift provides `protoc-gen-grpc-swift`, a program which is a plugin for the Protocol Buffers -compiler, `protoc`. - -> `protoc-gen-grpc-swift` only generates gRPC stubs, it doesn't generate messages. You must use -> `protoc-gen-swift` to generate messages in addition to gRPC Stubs. - -To generate gRPC stubs for your `.proto` files you must run the `protoc` command with -the `--grpc-swift_out=` option: - -```console -protoc --grpc-swift_out=. my-service.proto -``` - -The presence of `--grpc-swift_out` tells `protoc` to use the `protoc-gen-grpc-swift` plugin. By -default it'll look for the plugin in your `PATH`. You can also specify the path to the plugin -explicitly: - -```console -protoc --plugin=/path/to/protoc-gen-grpc-swift --grpc-swift_out=. my-service.proto -``` - -You can also specify various option the `protoc-gen-grpc-swift` via `protoc` using -the `--grpc-swift_opt` argument: - -```console -protoc --grpc-swift_opt== --grpc-swift_out=. -``` - -You can specify multiple options by passing the `--grpc-swift_opt` argument multiple times: - -```console -protoc \ - --grpc-swift_opt== \ - --grpc-swift_opt== \ - --grpc-swift_out=. -``` - -#### Generator options - -| Name | Possible Values | Default | Description | -|---------------------------|--------------------------------------------|------------|----------------------------------------------------------| -| `_V2` | `True`, `False` | `False` | Whether stubs are generated for gRPC Swift v2.x | -| `Visibility` | `Public`, `Package`, `Internal` | `Internal` | Access level for generated stubs | -| `Server` | `True`, `False` | `True` | Generate server stubs | -| `Client` | `True`, `False` | `True` | Generate client stubs | -| `FileNaming` | `FullPath`, `PathToUnderscore`, `DropPath` | `FullPath` | How generated source files should be named. (See below.) | -| `ProtoPathModuleMappings` | | | Path to module map `.asciipb` file. (See below.) | -| `AccessLevelOnImports` | `True`, `False` | `True` | Whether imports should have explicit access levels. | - -> The `protoc-gen-grpc-swift` binary is currently shared between gRPC Swift v1.x and v2.x. To -> generate stubs for v2.x you _must_ specify `_V2=True`. -> -> This option will be deprecated and removed once v2.x has been released. - -The `FileNaming` option has three possible values, for an input of `foo/bar/baz.proto` the following -output file will be generated: -- `FullPath`: `foo/bar/baz.grpc.swift`. -- `PathToUnderscore`: `foo_bar_baz.grpc.swift` -- `DropPath`: `baz.grpc.swift` - -The code generator assumes all inputs are generated into the same module, `ProtoPathModuleMappings` -allows you to specify a mapping from `.proto` files to the Swift module they are generated in. This -allows the code generator to add appropriate imports to your generated stubs. This is described in -more detail in the [SwiftProtobuf documentation](https://github.com/apple/swift-protobuf/blob/main/Documentation/PLUGIN.md). - -#### Building the plugin - -> The version of `protoc-gen-grpc-swift` you use mustn't be newer than the version of -> the `grpc-swift` you're using. - -If your package depends on `grpc-swift` then you can get a copy of `protoc-gen-grpc-swift` -by building it directly: - -```console -swift build --product protoc-gen-grpc-swift -``` - -This command will build the plugin into `.build/debug` directory. You can get the full path using -`swift build --show-bin-path`. diff --git a/Sources/GRPCCore/Documentation.docc/Development/Benchmarks.md b/Sources/GRPCCore/Documentation.docc/Development/Benchmarks.md deleted file mode 100644 index 4033857f7..000000000 --- a/Sources/GRPCCore/Documentation.docc/Development/Benchmarks.md +++ /dev/null @@ -1,35 +0,0 @@ -# Benchmarks - -## Overview - -Benchmarks for this package are in a separate Swift Package in the `Performance/Benchmarks` -subdirectory of the repository. - -They use the [`package-benchmark`](https://github.com/ordo-one/package-benchmark) plugin. -Benchmarks depends on the [`jemalloc`](https://jemalloc.net) memory allocation library, which is -used by `package-benchmark` to capture memory allocation statistics. - -An installation guide can be found in the [Getting Started article](https://swiftpackageindex.com/ordo-one/package-benchmark/documentation/benchmark/gettingstarted) -for `package-benchmark`. - -### Running the benchmarks - -You can run the benchmarks CLI by going to the `Performance/Benchmarks` subdirectory -(e.g. `cd Performance/Benchmarks`) and invoking: - -``` -swift package benchmark -``` - -Profiling benchmarks or building the benchmarks in release mode in Xcode with `jemalloc` isn't -currently supported and requires disabling `jemalloc`. - -Make sure you have quit Xcode and then open it from the command line with the `BENCHMARK_DISABLE_JEMALLOC=true` -environment variable set: - -``` -BENCHMARK_DISABLE_JEMALLOC=true xed . -``` - -For more information please refer to `swift package benchmark --help` or the [documentation -of `package-benchmark`](https://swiftpackageindex.com/ordo-one/package-benchmark/documentation/benchmark). diff --git a/Sources/GRPCCore/Documentation.docc/Documentation.md b/Sources/GRPCCore/Documentation.docc/Documentation.md deleted file mode 100644 index de33f0bb1..000000000 --- a/Sources/GRPCCore/Documentation.docc/Documentation.md +++ /dev/null @@ -1,37 +0,0 @@ -# ``GRPCCore`` - -A gRPC library for Swift written natively in Swift. - -> 🚧 This module is part of gRPC Swift v2 which is under active development and in the pre-release -> stage. - -## Package structure - -gRPC Swift is made up of a number of modules, each of which is documented separately. However this -module – ``GRPCCore`` – includes higher level documentation such as tutorials. The following list -contains products of this package: - -- ``GRPCCore`` contains core types and abstractions and is the 'base' module for the project. -- `GRPCInProcessTransport` contains an implementation of an in-process transport. -- `GRPCHTTP2TransportNIOPosix` provides client and server implementations of HTTP/2 transports built - on top of SwiftNIO's POSIX Sockets abstractions. -- `GRPCHTTP2TransportNIOTransportServices` provides client and server implementations of HTTP/2 - transports built on top of SwiftNIO's Network.framework abstraction, `NIOTransportServices`. -- `GRPCProtobuf` provides serialization and deserialization components for `SwiftProtobuf`. - -## Topics - -### Tutorials - -- -- - -### Essentials - -- - -### Getting involved - -Resources for developers working on gRPC Swift: - -- diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Hello-World/Hello-World.tutorial b/Sources/GRPCCore/Documentation.docc/Tutorials/Hello-World/Hello-World.tutorial deleted file mode 100644 index 32ecd4847..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Hello-World/Hello-World.tutorial +++ /dev/null @@ -1,131 +0,0 @@ -@Tutorial(time: 10) { - @XcodeRequirement( - title: "Xcode 16 Beta 5+", - destination: "https://developer.apple.com/download/" - ) - - @Intro(title: "Quick Start: Hello, World!") { - This tutorial walks you through the canonical "Hello, World!" program for gRPC Swift. You'll - learn how to implement a service using generated stubs, then you'll learn to configure and use - a gRPC server. You'll also see how to create a client and use it to call the server. - - The tutorial assumes you are comfortable with both Swift and the basic concepts of gRPC. If you - aren't then check out the Swift [Getting Started](https://www.swift.org/getting-started/) guide - and the [gRPC website](https://grpc.io) for more information. - - You'll need a local copy of the example code to work through this tutorial. Download the example - code from our GitHub repository if you haven't done so already. You can do this by cloning the - repository by running the following command in a terminal: - - ```console - git clone https://github.com/grpc/grpc-swift - ``` - - The rest of the tutorial assumes that your current working directory is the cloned `grpc-swift` - directory. - } - - @Section(title: "Run a gRPC application") { - Let's start by running the existing Greeter application. - - @Steps { - @Step { - In a terminal run `swift run hello-world serve` to start the server. By default it'll start - listening on port 31415. - - @Code(name: "Console.txt", file: "hello-world-sec02-step01.txt") - } - - @Step { - In another terminal run `swift run hello-world greet` to create a client, connect - to the server you started and send it a request and print the response. - - @Code(name: "Console.txt", file: "hello-world-sec02-step02.txt") - } - - @Step { - Congratulations! You've just run a client-server application with gRPC Swift. You can now - cancel the two running processes. - } - } - } - - @Section(title: "Update a gRPC service") { - Now let's look at how to update the application with an extra method on the server for the - client to call. Our gRPC service is defined using protocol buffers; you can find out lots more - about how to define a service in a `.proto` file in [What is gRPC?](https://grpc.io/docs/what-is-grpc/). - For now all you need to know is that both the server and client "stub" have a `SayHello` RPC - method that takes a `HelloRequest` parameter from the client and returns a `HelloReply` from - the server. - - @Steps { - @Step { - Open `HelloWorld.proto` in the `Examples/v2/hello-world` directory to see how the - service is defined. - - @Code(name: "HelloWorld.proto", file: "hello-world-sec03-step01.proto") - } - - @Step { - Let's update it so that the `Greeter` service has two methods. Add a new `SayHelloAgain` - method, with the same request and response types. - - @Code(name: "HelloWorld.proto", file: "hello-world-sec03-step02.proto") - } - } - } - - @Section(title: "Update and run the application") { - You need to regenerate the stubs as the service definition has changed. To do this run the - following command from the root of the checked out repository: - - ```console - Protos/generate.sh - ``` - - To learn how to generate stubs check out the article. - - Now that the stubs have been updated you need to implement and call the new method in the - human-written parts of your application. - - @Steps { - @Step { - Open `Serve.swift` in the `Examples/v2/hello-world/Subcommands` directory. - - @Code(name: "Serve.swift", file: "hello-world-sec04-step01.swift") - } - - @Step { - Implement the new method like this: - - @Code(name: "Serve.swift", file: "hello-world-sec04-step02.swift") - } - - @Step { - Let's update the client now. Open `Greet.swift` in the - `Examples/v2/hello-world/Subcommands` directory. - - @Code(name: "Greet.swift", file: "hello-world-sec04-step03.swift") - } - - @Step { - Add a call to the `sayHelloAgain` method: - - @Code(name: "Greet.swift", file: "hello-world-sec04-step04.swift") - } - - @Step { - Just like we did before, open a terminal and start the server by - running `swift run hello-world serve` - - @Code(name: "Console.txt", file: "hello-world-sec04-step05.txt") - } - - @Step { - In a separate terminal run `swift run hello-world greet` to call the server. - - @Code(name: "Console.txt", file: "hello-world-sec04-step06.txt") - } - } - } -} diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Hello-World/Resources/hello-world-sec02-step01.txt b/Sources/GRPCCore/Documentation.docc/Tutorials/Hello-World/Resources/hello-world-sec02-step01.txt deleted file mode 100644 index 4d6dd6c92..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Hello-World/Resources/hello-world-sec02-step01.txt +++ /dev/null @@ -1 +0,0 @@ -Greeter listening on [ipv4]127.0.0.1:31415 diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Hello-World/Resources/hello-world-sec02-step02.txt b/Sources/GRPCCore/Documentation.docc/Tutorials/Hello-World/Resources/hello-world-sec02-step02.txt deleted file mode 100644 index 627d37bbc..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Hello-World/Resources/hello-world-sec02-step02.txt +++ /dev/null @@ -1 +0,0 @@ -Hello, stranger diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Hello-World/Resources/hello-world-sec03-step01.proto b/Sources/GRPCCore/Documentation.docc/Tutorials/Hello-World/Resources/hello-world-sec03-step01.proto deleted file mode 100644 index cdeb0ec5a..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Hello-World/Resources/hello-world-sec03-step01.proto +++ /dev/null @@ -1,15 +0,0 @@ -// The greeting service definition. -service Greeter { - // Sends a greeting. - rpc SayHello (HelloRequest) returns (HelloReply) {} -} - -// The request message containing the user's name. -message HelloRequest { - string name = 1; -} - -// The response message containing the greetings. -message HelloReply { - string message = 1; -} diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Hello-World/Resources/hello-world-sec03-step02.proto b/Sources/GRPCCore/Documentation.docc/Tutorials/Hello-World/Resources/hello-world-sec03-step02.proto deleted file mode 100644 index a6dbcfd13..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Hello-World/Resources/hello-world-sec03-step02.proto +++ /dev/null @@ -1,17 +0,0 @@ -// The greeting service definition. -service Greeter { - // Sends a greeting. - rpc SayHello (HelloRequest) returns (HelloReply) {} - // Sends another greeting. - rpc SayHelloAgain (HelloRequest) returns (HelloReply) {} -} - -// The request message containing the user's name. -message HelloRequest { - string name = 1; -} - -// The response message containing the greetings. -message HelloReply { - string message = 1; -} diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Hello-World/Resources/hello-world-sec04-step01.swift b/Sources/GRPCCore/Documentation.docc/Tutorials/Hello-World/Resources/hello-world-sec04-step01.swift deleted file mode 100644 index 2c89bab8d..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Hello-World/Resources/hello-world-sec04-step01.swift +++ /dev/null @@ -1,10 +0,0 @@ -struct Greeter: Helloworld_GreeterServiceProtocol { - func sayHello( - request: ServerRequest.Single - ) async throws -> ServerResponse.Single { - var reply = Helloworld_HelloReply() - let recipient = request.message.name.isEmpty ? "stranger" : request.message.name - reply.message = "Hello, \(recipient)" - return ServerResponse.Single(message: reply) - } -} diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Hello-World/Resources/hello-world-sec04-step02.swift b/Sources/GRPCCore/Documentation.docc/Tutorials/Hello-World/Resources/hello-world-sec04-step02.swift deleted file mode 100644 index e9dde27f9..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Hello-World/Resources/hello-world-sec04-step02.swift +++ /dev/null @@ -1,19 +0,0 @@ -struct Greeter: Helloworld_GreeterServiceProtocol { - func sayHello( - request: ServerRequest.Single - ) async throws -> ServerResponse.Single { - var reply = Helloworld_HelloReply() - let recipient = request.message.name.isEmpty ? "stranger" : request.message.name - reply.message = "Hello, \(recipient)" - return ServerResponse.Single(message: reply) - } - - func sayHelloAgain( - request: ServerRequest.Single - ) async throws -> ServerResponse.Single { - var reply = Helloworld_HelloReply() - let recipient = request.message.name.isEmpty ? "stranger" : request.message.name - reply.message = "Hello again, \(recipient)" - return ServerResponse.Single(message: reply) - } -} diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Hello-World/Resources/hello-world-sec04-step03.swift b/Sources/GRPCCore/Documentation.docc/Tutorials/Hello-World/Resources/hello-world-sec04-step03.swift deleted file mode 100644 index 4e10d4adc..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Hello-World/Resources/hello-world-sec04-step03.swift +++ /dev/null @@ -1,3 +0,0 @@ -let greeter = Helloworld_GreeterClient(wrapping: client) -let reply = try await greeter.sayHello(.with { $0.name = self.name }) -print(reply.message) diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Hello-World/Resources/hello-world-sec04-step04.swift b/Sources/GRPCCore/Documentation.docc/Tutorials/Hello-World/Resources/hello-world-sec04-step04.swift deleted file mode 100644 index 534a4fb8a..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Hello-World/Resources/hello-world-sec04-step04.swift +++ /dev/null @@ -1,6 +0,0 @@ -let greeter = Helloworld_GreeterClient(wrapping: client) -let reply = try await greeter.sayHello(.with { $0.name = self.name }) -print(reply.message) - -let replyAgain = try await greeter.sayHelloAgain(.with { $0.name = self.name }) -print(replyAgain.message) diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Hello-World/Resources/hello-world-sec04-step05.txt b/Sources/GRPCCore/Documentation.docc/Tutorials/Hello-World/Resources/hello-world-sec04-step05.txt deleted file mode 100644 index 4d6dd6c92..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Hello-World/Resources/hello-world-sec04-step05.txt +++ /dev/null @@ -1 +0,0 @@ -Greeter listening on [ipv4]127.0.0.1:31415 diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Hello-World/Resources/hello-world-sec04-step06.txt b/Sources/GRPCCore/Documentation.docc/Tutorials/Hello-World/Resources/hello-world-sec04-step06.txt deleted file mode 100644 index 5e45eab71..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Hello-World/Resources/hello-world-sec04-step06.txt +++ /dev/null @@ -1,2 +0,0 @@ -Hello, stranger -Hello again, stranger diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Resources/image.png b/Sources/GRPCCore/Documentation.docc/Tutorials/Resources/image.png deleted file mode 100644 index 909c66db1..000000000 Binary files a/Sources/GRPCCore/Documentation.docc/Tutorials/Resources/image.png and /dev/null differ diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec01-step01-mkdir.txt b/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec01-step01-mkdir.txt deleted file mode 100644 index f20a12d5a..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec01-step01-mkdir.txt +++ /dev/null @@ -1 +0,0 @@ -$ mkdir RouteGuide diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec01-step02-cd.txt b/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec01-step02-cd.txt deleted file mode 100644 index f92e41733..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec01-step02-cd.txt +++ /dev/null @@ -1 +0,0 @@ -$ cd RouteGuide diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec01-step03-mkdir.txt b/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec01-step03-mkdir.txt deleted file mode 100644 index bb89324f6..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec01-step03-mkdir.txt +++ /dev/null @@ -1 +0,0 @@ -$ mkdir Sources Protos diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec01-step04-tools-version.swift b/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec01-step04-tools-version.swift deleted file mode 100644 index d99076027..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec01-step04-tools-version.swift +++ /dev/null @@ -1 +0,0 @@ -// swift-tools-version: 6.0 diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec01-step05-import.swift b/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec01-step05-import.swift deleted file mode 100644 index f41662016..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec01-step05-import.swift +++ /dev/null @@ -1,2 +0,0 @@ -// swift-tools-version: 6.0 -import PackageDescription diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec01-step06-description.swift b/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec01-step06-description.swift deleted file mode 100644 index 0219d2c28..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec01-step06-description.swift +++ /dev/null @@ -1,8 +0,0 @@ -// swift-tools-version: 6.0 -import PackageDescription - -let package = Package( - name: "RouteGuide", - dependencies: [], - targets: [] -) diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec01-step07-description.swift b/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec01-step07-description.swift deleted file mode 100644 index baaffab21..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec01-step07-description.swift +++ /dev/null @@ -1,12 +0,0 @@ -// swift-tools-version: 6.0 -import PackageDescription - -let package = Package( - name: "RouteGuide", - platforms: [.macOS(.v15)], - dependencies: [ - .package(url: "https://github.com/grpc/grpc-swift", branch: "main"), - .package(url: "https://github.com/apple/swift-protobuf", from: "1.27.0"), - ], - targets: [] -) diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec01-step08-description.swift b/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec01-step08-description.swift deleted file mode 100644 index c9f71095f..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec01-step08-description.swift +++ /dev/null @@ -1,21 +0,0 @@ -// swift-tools-version: 6.0 -import PackageDescription - -let package = Package( - name: "RouteGuide", - platforms: [.macOS(.v15)], - dependencies: [ - .package(url: "https://github.com/grpc/grpc-swift", branch: "main"), - .package(url: "https://github.com/apple/swift-protobuf", from: "1.27.0"), - ], - targets: [ - .executableTarget( - name: "RouteGuide", - dependencies: [ - .product(name: "_GRPCHTTP2Transport", package: "grpc-swift"), - .product(name: "_GRPCProtobuf", package: "grpc-swift"), - .product(name: "SwiftProtobuf", package: "swift-protobuf"), - ] - ) - ] -) diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec02-step01-import.proto b/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec02-step01-import.proto deleted file mode 100644 index 676449318..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec02-step01-import.proto +++ /dev/null @@ -1,3 +0,0 @@ -syntax = "proto3"; - -package routeguide; diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec02-step02-service.proto b/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec02-step02-service.proto deleted file mode 100644 index cd37f3372..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec02-step02-service.proto +++ /dev/null @@ -1,7 +0,0 @@ -syntax = "proto3"; - -package routeguide; - -service RouteGuide { - // ... -} diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec02-step03-unary.proto b/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec02-step03-unary.proto deleted file mode 100644 index a34c123c5..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec02-step03-unary.proto +++ /dev/null @@ -1,8 +0,0 @@ -syntax = "proto3"; - -package routeguide; - -service RouteGuide { - // Obtains the feature at a given position. - rpc GetFeature(Point) returns (Feature) {} -} diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec02-step04-server-streaming.proto b/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec02-step04-server-streaming.proto deleted file mode 100644 index ce0a5a66d..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec02-step04-server-streaming.proto +++ /dev/null @@ -1,14 +0,0 @@ -syntax = "proto3"; - -package routeguide; - -service RouteGuide { - // Obtains the feature at a given position. - rpc GetFeature(Point) returns (Feature) {} - - // Obtains the Features available within the given Rectangle. Results are - // streamed rather than returned at once (e.g. in a response message with a - // repeated field), as the rectangle may cover a large area and contain a - // huge number of features. - rpc ListFeatures(Rectangle) returns (stream Feature) {} -} diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec02-step05-client-streaming.proto b/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec02-step05-client-streaming.proto deleted file mode 100644 index 1e8d494e3..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec02-step05-client-streaming.proto +++ /dev/null @@ -1,18 +0,0 @@ -syntax = "proto3"; - -package routeguide; - -service RouteGuide { - // Obtains the feature at a given position. - rpc GetFeature(Point) returns (Feature) {} - - // Obtains the Features available within the given Rectangle. Results are - // streamed rather than returned at once (e.g. in a response message with a - // repeated field), as the rectangle may cover a large area and contain a - // huge number of features. - rpc ListFeatures(Rectangle) returns (stream Feature) {} - - // Accepts a stream of Points on a route being traversed, returning a - // RouteSummary when traversal is completed. - rpc RecordRoute(stream Point) returns (RouteSummary) {} -} diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec02-step06-bidi-streaming.proto b/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec02-step06-bidi-streaming.proto deleted file mode 100644 index 01a0b3932..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec02-step06-bidi-streaming.proto +++ /dev/null @@ -1,22 +0,0 @@ -syntax = "proto3"; - -package routeguide; - -service RouteGuide { - // Obtains the feature at a given position. - rpc GetFeature(Point) returns (Feature) {} - - // Obtains the Features available within the given Rectangle. Results are - // streamed rather than returned at once (e.g. in a response message with a - // repeated field), as the rectangle may cover a large area and contain a - // huge number of features. - rpc ListFeatures(Rectangle) returns (stream Feature) {} - - // Accepts a stream of Points on a route being traversed, returning a - // RouteSummary when traversal is completed. - rpc RecordRoute(stream Point) returns (RouteSummary) {} - - // Accepts a stream of RouteNotes sent while a route is being traversed, - // while receiving other RouteNotes (e.g. from other users). - rpc RouteChat(stream RouteNote) returns (stream RouteNote) {} -} diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec02-step07-messages.proto b/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec02-step07-messages.proto deleted file mode 100644 index 435d7878f..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec02-step07-messages.proto +++ /dev/null @@ -1,80 +0,0 @@ -syntax = "proto3"; - -package routeguide; - -service RouteGuide { - // Obtains the feature at a given position. - rpc GetFeature(Point) returns (Feature) {} - - // Obtains the Features available within the given Rectangle. Results are - // streamed rather than returned at once (e.g. in a response message with a - // repeated field), as the rectangle may cover a large area and contain a - // huge number of features. - rpc ListFeatures(Rectangle) returns (stream Feature) {} - - // Accepts a stream of Points on a route being traversed, returning a - // RouteSummary when traversal is completed. - rpc RecordRoute(stream Point) returns (RouteSummary) {} - - // Accepts a stream of RouteNotes sent while a route is being traversed, - // while receiving other RouteNotes (e.g. from other users). - rpc RouteChat(stream RouteNote) returns (stream RouteNote) {} -} - -// Points are represented as latitude-longitude pairs in the E7 representation -// (degrees multiplied by 10**7 and rounded to the nearest integer). -// Latitudes should be in the range +/- 90 degrees and longitude should be in -// the range +/- 180 degrees (inclusive). -message Point { - int32 latitude = 1; - int32 longitude = 2; -} - -// A latitude-longitude rectangle, represented as two diagonally opposite -// points "lo" and "hi". -message Rectangle { - // One corner of the rectangle. - Point lo = 1; - - // The other corner of the rectangle. - Point hi = 2; -} - -// A feature names something at a given point. -// -// If a feature could not be named, the name is empty. -message Feature { - // The name of the feature. - string name = 1; - - // The point where the feature is detected. - Point location = 2; -} - -// A RouteNote is a message sent while at a given point. -message RouteNote { - // The location from which the message is sent. - Point location = 1; - - // The message to be sent. - string message = 2; -} - -// A RouteSummary is received in response to a RecordRoute rpc. -// -// It contains the number of individual points received, the number of -// detected features, and the total distance covered as the cumulative sum of -// the distance between each point. -message RouteSummary { - // The number of points received. - int32 point_count = 1; - - // The number of known features passed while traversing the route. - int32 feature_count = 2; - - // The distance covered in metres. - int32 distance = 3; - - // The duration of the traversal in seconds. - int32 elapsed_time = 4; -} diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec03-step01-protoc-plugins.txt b/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec03-step01-protoc-plugins.txt deleted file mode 100644 index 4eabab72c..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec03-step01-protoc-plugins.txt +++ /dev/null @@ -1,2 +0,0 @@ -$ swift build --product protoc-gen-swift -$ swift build --product protoc-gen-grpc-swift diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec03-step02-mkdir.txt b/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec03-step02-mkdir.txt deleted file mode 100644 index fa5470b47..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec03-step02-mkdir.txt +++ /dev/null @@ -1 +0,0 @@ -$ mkdir Sources/Generated diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec03-step03-gen-messages.txt b/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec03-step03-gen-messages.txt deleted file mode 100644 index 5060b92b4..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec03-step03-gen-messages.txt +++ /dev/null @@ -1,4 +0,0 @@ -$ protoc --plugin=.build/debug/protoc-gen-swift \ - -I Protos \ - --swift_out=Sources/Generated \ - Protos/route_guide.proto diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec03-step04-gen-grpc.txt b/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec03-step04-gen-grpc.txt deleted file mode 100644 index ab2c22fd8..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec03-step04-gen-grpc.txt +++ /dev/null @@ -1,5 +0,0 @@ -$ protoc --plugin=.build/debug/protoc-gen-grpc-swift \ - -I Protos \ - --grpc-swift_out=Sources/Generated \ - --grpc-swift_opt=_V2=true \ - Protos/route_guide.proto diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec04-step01-struct.swift b/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec04-step01-struct.swift deleted file mode 100644 index 65aa33cb2..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec04-step01-struct.swift +++ /dev/null @@ -1,4 +0,0 @@ -import GRPCCore - -struct RouteGuideService: Routeguide_RouteGuide.ServiceProtocol { -} diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec04-step02-unimplemented.swift b/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec04-step02-unimplemented.swift deleted file mode 100644 index de6f415cd..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec04-step02-unimplemented.swift +++ /dev/null @@ -1,23 +0,0 @@ -import GRPCCore - -struct RouteGuideService: Routeguide_RouteGuide.ServiceProtocol { - func getFeature( - request: ServerRequest.Single - ) async throws -> ServerResponse.Single { - } - - func listFeatures( - request: ServerRequest.Single - ) async throws -> ServerResponse.Stream { - } - - func recordRoute( - request: ServerRequest.Stream - ) async throws -> ServerResponse.Single { - } - - func routeChat( - request: ServerRequest.Stream - ) async throws -> ServerResponse.Stream { - } -} diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec04-step03-features.swift b/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec04-step03-features.swift deleted file mode 100644 index 99bb69ad7..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec04-step03-features.swift +++ /dev/null @@ -1,32 +0,0 @@ -import GRPCCore - -struct RouteGuideService: Routeguide_RouteGuide.ServiceProtocol { - /// Known features. - private let features: [Routeguide_Feature] - - /// Creates a new route guide service. - /// - Parameter features: Known features. - init(features: [Routeguide_Feature]) { - self.features = features - } - - func getFeature( - request: ServerRequest.Single - ) async throws -> ServerResponse.Single { - } - - func listFeatures( - request: ServerRequest.Single - ) async throws -> ServerResponse.Stream { - } - - func recordRoute( - request: ServerRequest.Stream - ) async throws -> ServerResponse.Single { - } - - func routeChat( - request: ServerRequest.Stream - ) async throws -> ServerResponse.Stream { - } -} diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec04-step04-unary.swift b/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec04-step04-unary.swift deleted file mode 100644 index cf791d6f8..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec04-step04-unary.swift +++ /dev/null @@ -1,43 +0,0 @@ -import GRPCCore - -struct RouteGuideService: Routeguide_RouteGuide.ServiceProtocol { - /// Known features. - private let features: [Routeguide_Feature] - - /// Creates a new route guide service. - /// - Parameter features: Known features. - init(features: [Routeguide_Feature]) { - self.features = features - } - - /// Returns the first feature found at the given location, if one exists. - private func findFeature(latitude: Int32, longitude: Int32) -> Routeguide_Feature? { - self.features.first { - $0.location.latitude == latitude && $0.location.longitude == longitude - } - } - - func getFeature( - request: ServerRequest.Single - ) async throws -> ServerResponse.Single { - let feature = self.findFeature( - latitude: request.message.latitude, - longitude: request.message.longitude - ) - } - - func listFeatures( - request: ServerRequest.Single - ) async throws -> ServerResponse.Stream { - } - - func recordRoute( - request: ServerRequest.Stream - ) async throws -> ServerResponse.Single { - } - - func routeChat( - request: ServerRequest.Stream - ) async throws -> ServerResponse.Stream { - } -} diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec04-step05-unary.swift b/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec04-step05-unary.swift deleted file mode 100644 index 452c74a85..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec04-step05-unary.swift +++ /dev/null @@ -1,57 +0,0 @@ -import GRPCCore - -struct RouteGuideService: Routeguide_RouteGuide.ServiceProtocol { - /// Known features. - private let features: [Routeguide_Feature] - - /// Creates a new route guide service. - /// - Parameter features: Known features. - init(features: [Routeguide_Feature]) { - self.features = features - } - - /// Returns the first feature found at the given location, if one exists. - private func findFeature(latitude: Int32, longitude: Int32) -> Routeguide_Feature? { - self.features.first { - $0.location.latitude == latitude && $0.location.longitude == longitude - } - } - - func getFeature( - request: ServerRequest.Single - ) async throws -> ServerResponse.Single { - let feature = self.findFeature( - latitude: request.message.latitude, - longitude: request.message.longitude - ) - - if let feature { - return ServerResponse.Single(message: feature) - } else { - // No feature: return a feature with an empty name. - let unknownFeature = Routeguide_Feature.with { - $0.name = "" - $0.location = .with { - $0.latitude = request.message.latitude - $0.longitude = request.message.longitude - } - } - return ServerResponse.Single(message: unknownFeature) - } - } - - func listFeatures( - request: ServerRequest.Single - ) async throws -> ServerResponse.Stream { - } - - func recordRoute( - request: ServerRequest.Stream - ) async throws -> ServerResponse.Single { - } - - func routeChat( - request: ServerRequest.Stream - ) async throws -> ServerResponse.Stream { - } -} diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec04-step06-server-streaming.swift b/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec04-step06-server-streaming.swift deleted file mode 100644 index b52288fa3..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec04-step06-server-streaming.swift +++ /dev/null @@ -1,73 +0,0 @@ -import GRPCCore - -struct RouteGuideService: Routeguide_RouteGuide.ServiceProtocol { - /// Known features. - private let features: [Routeguide_Feature] - - /// Creates a new route guide service. - /// - Parameter features: Known features. - init(features: [Routeguide_Feature]) { - self.features = features - } - - /// Returns the first feature found at the given location, if one exists. - private func findFeature(latitude: Int32, longitude: Int32) -> Routeguide_Feature? { - self.features.first { - $0.location.latitude == latitude && $0.location.longitude == longitude - } - } - - func getFeature( - request: ServerRequest.Single - ) async throws -> ServerResponse.Single { - let feature = self.findFeature( - latitude: request.message.latitude, - longitude: request.message.longitude - ) - - if let feature { - return ServerResponse.Single(message: feature) - } else { - // No feature: return a feature with an empty name. - let unknownFeature = Routeguide_Feature.with { - $0.name = "" - $0.location = .with { - $0.latitude = request.message.latitude - $0.longitude = request.message.longitude - } - } - return ServerResponse.Single(message: unknownFeature) - } - } - - func listFeatures( - request: ServerRequest.Single - ) async throws -> ServerResponse.Stream { - return ServerResponse.Stream { writer in - for feature in self.features { - if !feature.name.isEmpty, feature.isContained(by: request.message) { - try await writer.write(feature) - } - } - } - } - - func recordRoute( - request: ServerRequest.Stream - ) async throws -> ServerResponse.Single { - } - - func routeChat( - request: ServerRequest.Stream - ) async throws -> ServerResponse.Stream { - } -} - -extension Routeguide_Feature { - func isContained(by rectangle: Routeguide_Rectangle) -> Bool { - return rectangle.lo.latitude <= self.location.latitude - && self.location.latitude <= rectangle.hi.latitude - && rectangle.lo.longitude <= self.location.longitude - && self.location.longitude <= rectangle.hi.longitude - } -} diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec04-step07-server-streaming.swift b/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec04-step07-server-streaming.swift deleted file mode 100644 index 780c63a14..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec04-step07-server-streaming.swift +++ /dev/null @@ -1,75 +0,0 @@ -import GRPCCore - -struct RouteGuideService: Routeguide_RouteGuide.ServiceProtocol { - /// Known features. - private let features: [Routeguide_Feature] - - /// Creates a new route guide service. - /// - Parameter features: Known features. - init(features: [Routeguide_Feature]) { - self.features = features - } - - /// Returns the first feature found at the given location, if one exists. - private func findFeature(latitude: Int32, longitude: Int32) -> Routeguide_Feature? { - self.features.first { - $0.location.latitude == latitude && $0.location.longitude == longitude - } - } - - func getFeature( - request: ServerRequest.Single - ) async throws -> ServerResponse.Single { - let feature = self.findFeature( - latitude: request.message.latitude, - longitude: request.message.longitude - ) - - if let feature { - return ServerResponse.Single(message: feature) - } else { - // No feature: return a feature with an empty name. - let unknownFeature = Routeguide_Feature.with { - $0.name = "" - $0.location = .with { - $0.latitude = request.message.latitude - $0.longitude = request.message.longitude - } - } - return ServerResponse.Single(message: unknownFeature) - } - } - - func listFeatures( - request: ServerRequest.Single - ) async throws -> ServerResponse.Stream { - return ServerResponse.Stream { writer in - for feature in self.features { - if !feature.name.isEmpty, feature.isContained(by: request.message) { - try await writer.write(feature) - } - } - - return [:] - } - } - - func recordRoute( - request: ServerRequest.Stream - ) async throws -> ServerResponse.Single { - } - - func routeChat( - request: ServerRequest.Stream - ) async throws -> ServerResponse.Stream { - } -} - -extension Routeguide_Feature { - func isContained(by rectangle: Routeguide_Rectangle) -> Bool { - return rectangle.lo.latitude <= self.location.latitude - && self.location.latitude <= rectangle.hi.latitude - && rectangle.lo.longitude <= self.location.longitude - && self.location.longitude <= rectangle.hi.longitude - } -} diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec04-step08-client-streaming.swift b/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec04-step08-client-streaming.swift deleted file mode 100644 index 76f399c50..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec04-step08-client-streaming.swift +++ /dev/null @@ -1,154 +0,0 @@ -import Foundation -import GRPCCore - -struct RouteGuideService: Routeguide_RouteGuide.ServiceProtocol { - /// Known features. - private let features: [Routeguide_Feature] - - /// Creates a new route guide service. - /// - Parameter features: Known features. - init(features: [Routeguide_Feature]) { - self.features = features - } - - /// Returns the first feature found at the given location, if one exists. - private func findFeature(latitude: Int32, longitude: Int32) -> Routeguide_Feature? { - self.features.first { - $0.location.latitude == latitude && $0.location.longitude == longitude - } - } - - func getFeature( - request: ServerRequest.Single - ) async throws -> ServerResponse.Single { - let feature = self.findFeature( - latitude: request.message.latitude, - longitude: request.message.longitude - ) - - if let feature { - return ServerResponse.Single(message: feature) - } else { - // No feature: return a feature with an empty name. - let unknownFeature = Routeguide_Feature.with { - $0.name = "" - $0.location = .with { - $0.latitude = request.message.latitude - $0.longitude = request.message.longitude - } - } - return ServerResponse.Single(message: unknownFeature) - } - } - - func listFeatures( - request: ServerRequest.Single - ) async throws -> ServerResponse.Stream { - return ServerResponse.Stream { writer in - for feature in self.features { - if !feature.name.isEmpty, feature.isContained(by: request.message) { - try await writer.write(feature) - } - } - - return [:] - } - } - - func recordRoute( - request: ServerRequest.Stream - ) async throws -> ServerResponse.Single { - let startTime = ContinuousClock.now - var pointsVisited = 0 - var featuresVisited = 0 - var distanceTravelled = 0.0 - var previousPoint: Routeguide_Point? = nil - - for try await point in request.messages { - pointsVisited += 1 - - if self.findFeature(latitude: point.latitude, longitude: point.longitude) != nil { - featuresVisited += 1 - } - - if let previousPoint { - distanceTravelled += greatCircleDistance(from: previousPoint, to: point) - } - - previousPoint = point - } - - let duration = startTime.duration(to: .now) - let summary = Routeguide_RouteSummary.with { - $0.pointCount = Int32(pointsVisited) - $0.featureCount = Int32(featuresVisited) - $0.elapsedTime = Int32(duration.components.seconds) - $0.distance = Int32(distanceTravelled) - } - - return ServerResponse.Single(message: summary) - } - - func routeChat( - request: ServerRequest.Stream - ) async throws -> ServerResponse.Stream { - } -} - -extension Routeguide_Feature { - func isContained(by rectangle: Routeguide_Rectangle) -> Bool { - return rectangle.lo.latitude <= self.location.latitude - && self.location.latitude <= rectangle.hi.latitude - && rectangle.lo.longitude <= self.location.longitude - && self.location.longitude <= rectangle.hi.longitude - } -} - -private func greatCircleDistance( - from point1: Routeguide_Point, - to point2: Routeguide_Point -) -> Double { - // See https://en.wikipedia.org/wiki/Great-circle_distance - // - // Let λ1 (lambda1) and φ1 (phi1) be the longitude and latitude of point 1. - // Let λ2 (lambda2) and φ2 (phi2) be the longitude and latitude of point 2. - // - // Let Δλ = λ2 - λ1, and Δφ = φ2 - φ1. - // - // The central angle between them, σc (sigmaC) can be computed as: - // - // σc = 2 ⨯ sqrt(sin²(Δφ/2) + cos(φ1) ⨯ cos(φ2) ⨯ sin²(Δλ/2)) - // - // The unit distance (d) between point1 and point2 can then be computed as: - // - // d = 2 ⨯ atan(sqrt(σc), sqrt(1 - σc)) - - let lambda1 = radians(degreesInE7: point1.longitude) - let phi1 = radians(degreesInE7: point1.latitude) - let lambda2 = radians(degreesInE7: point2.longitude) - let phi2 = radians(degreesInE7: point2.latitude) - - // Δλ = λ2 - λ1 - let deltaLambda = lambda2 - lambda1 - // Δφ = φ2 - φ1 - let deltaPhi = phi2 - phi1 - - // sin²(Δφ/2) - let sinHalfDeltaPhiSquared = sin(deltaPhi / 2) * sin(deltaPhi / 2) - // sin²(Δλ/2) - let sinHalfDeltaLambdaSquared = sin(deltaLambda / 2) * sin(deltaLambda / 2) - - // σc = 2 ⨯ sqrt(sin²(Δφ/2) + cos(φ1) ⨯ cos(φ2) ⨯ sin²(Δλ/2)) - let sigmaC = 2 * sqrt(sinHalfDeltaPhiSquared + cos(phi1) * cos(phi2) * sinHalfDeltaLambdaSquared) - - // This is the unit distance, i.e. assumes the circle has a radius of 1. - let unitDistance = 2 * atan2(sqrt(sigmaC), sqrt(1 - sigmaC)) - - // Scale it by the radius of the Earth in meters. - let earthRadius = 6_371_000.0 - return unitDistance * earthRadius -} - -private func radians(degreesInE7 degrees: Int32) -> Double { - return (Double(degrees) / 1e7) * .pi / 180.0 -} diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec04-step09-bidi-streaming.swift b/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec04-step09-bidi-streaming.swift deleted file mode 100644 index 5c8ad560d..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec04-step09-bidi-streaming.swift +++ /dev/null @@ -1,185 +0,0 @@ -import Foundation -import GRPCCore -import Synchronization - -struct RouteGuideService: Routeguide_RouteGuide.ServiceProtocol { - /// Known features. - private let features: [Routeguide_Feature] - - /// Notes recorded by clients. - private let receivedNotes: Notes - - /// A thread-safe store for notes sent by clients. - private final class Notes: Sendable { - private let notes: Mutex<[Routeguide_RouteNote]> - - init() { - self.notes = Mutex([]) - } - - /// Records a note and returns all other notes recorded at the same location. - /// - /// - Parameter receivedNote: A note to record. - /// - Returns: Other notes recorded at the same location. - func recordNote(_ receivedNote: Routeguide_RouteNote) -> [Routeguide_RouteNote] { - return self.notes.withLock { notes in - var notesFromSameLocation: [Routeguide_RouteNote] = [] - for note in notes { - if note.location == receivedNote.location { - notesFromSameLocation.append(note) - } - } - notes.append(receivedNote) - return notesFromSameLocation - } - } - } - - /// Creates a new route guide service. - /// - Parameter features: Known features. - init(features: [Routeguide_Feature]) { - self.features = features - self.receivedNotes = Notes() - } - - /// Returns the first feature found at the given location, if one exists. - private func findFeature(latitude: Int32, longitude: Int32) -> Routeguide_Feature? { - self.features.first { - $0.location.latitude == latitude && $0.location.longitude == longitude - } - } - - func getFeature( - request: ServerRequest.Single - ) async throws -> ServerResponse.Single { - let feature = self.findFeature( - latitude: request.message.latitude, - longitude: request.message.longitude - ) - - if let feature { - return ServerResponse.Single(message: feature) - } else { - // No feature: return a feature with an empty name. - let unknownFeature = Routeguide_Feature.with { - $0.name = "" - $0.location = .with { - $0.latitude = request.message.latitude - $0.longitude = request.message.longitude - } - } - return ServerResponse.Single(message: unknownFeature) - } - } - - func listFeatures( - request: ServerRequest.Single - ) async throws -> ServerResponse.Stream { - return ServerResponse.Stream { writer in - for feature in self.features { - if !feature.name.isEmpty, feature.isContained(by: request.message) { - try await writer.write(feature) - } - } - - return [:] - } - } - - func recordRoute( - request: ServerRequest.Stream - ) async throws -> ServerResponse.Single { - let startTime = ContinuousClock.now - var pointsVisited = 0 - var featuresVisited = 0 - var distanceTravelled = 0.0 - var previousPoint: Routeguide_Point? = nil - - for try await point in request.messages { - pointsVisited += 1 - - if self.findFeature(latitude: point.latitude, longitude: point.longitude) != nil { - featuresVisited += 1 - } - - if let previousPoint { - distanceTravelled += greatCircleDistance(from: previousPoint, to: point) - } - - previousPoint = point - } - - let duration = startTime.duration(to: .now) - let summary = Routeguide_RouteSummary.with { - $0.pointCount = Int32(pointsVisited) - $0.featureCount = Int32(featuresVisited) - $0.elapsedTime = Int32(duration.components.seconds) - $0.distance = Int32(distanceTravelled) - } - - return ServerResponse.Single(message: summary) - } - - func routeChat( - request: ServerRequest.Stream - ) async throws -> ServerResponse.Stream { - } -} - -extension Routeguide_Feature { - func isContained(by rectangle: Routeguide_Rectangle) -> Bool { - return rectangle.lo.latitude <= self.location.latitude - && self.location.latitude <= rectangle.hi.latitude - && rectangle.lo.longitude <= self.location.longitude - && self.location.longitude <= rectangle.hi.longitude - } -} - -private func greatCircleDistance( - from point1: Routeguide_Point, - to point2: Routeguide_Point -) -> Double { - // See https://en.wikipedia.org/wiki/Great-circle_distance - // - // Let λ1 (lambda1) and φ1 (phi1) be the longitude and latitude of point 1. - // Let λ2 (lambda2) and φ2 (phi2) be the longitude and latitude of point 2. - // - // Let Δλ = λ2 - λ1, and Δφ = φ2 - φ1. - // - // The central angle between them, σc (sigmaC) can be computed as: - // - // σc = 2 ⨯ sqrt(sin²(Δφ/2) + cos(φ1) ⨯ cos(φ2) ⨯ sin²(Δλ/2)) - // - // The unit distance (d) between point1 and point2 can then be computed as: - // - // d = 2 ⨯ atan(sqrt(σc), sqrt(1 - σc)) - - let lambda1 = radians(degreesInE7: point1.longitude) - let phi1 = radians(degreesInE7: point1.latitude) - let lambda2 = radians(degreesInE7: point2.longitude) - let phi2 = radians(degreesInE7: point2.latitude) - - // Δλ = λ2 - λ1 - let deltaLambda = lambda2 - lambda1 - // Δφ = φ2 - φ1 - let deltaPhi = phi2 - phi1 - - // sin²(Δφ/2) - let sinHalfDeltaPhiSquared = sin(deltaPhi / 2) * sin(deltaPhi / 2) - // sin²(Δλ/2) - let sinHalfDeltaLambdaSquared = sin(deltaLambda / 2) * sin(deltaLambda / 2) - - // σc = 2 ⨯ sqrt(sin²(Δφ/2) + cos(φ1) ⨯ cos(φ2) ⨯ sin²(Δλ/2)) - let sigmaC = 2 * sqrt(sinHalfDeltaPhiSquared + cos(phi1) * cos(phi2) * sinHalfDeltaLambdaSquared) - - // This is the unit distance, i.e. assumes the circle has a radius of 1. - let unitDistance = 2 * atan2(sqrt(sigmaC), sqrt(1 - sigmaC)) - - // Scale it by the radius of the Earth in meters. - let earthRadius = 6_371_000.0 - return unitDistance * earthRadius -} - -private func radians(degreesInE7 degrees: Int32) -> Double { - return (Double(degrees) / 1e7) * .pi / 180.0 -} diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec04-step10-bidi-streaming.swift b/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec04-step10-bidi-streaming.swift deleted file mode 100644 index 3913b0c36..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec04-step10-bidi-streaming.swift +++ /dev/null @@ -1,192 +0,0 @@ -import Foundation -import GRPCCore -import Synchronization - -struct RouteGuideService: Routeguide_RouteGuide.ServiceProtocol { - /// Known features. - private let features: [Routeguide_Feature] - - /// Notes recorded by clients. - private let receivedNotes: Notes - - /// A thread-safe store for notes sent by clients. - private final class Notes: Sendable { - private let notes: Mutex<[Routeguide_RouteNote]> - - init() { - self.notes = Mutex([]) - } - - /// Records a note and returns all other notes recorded at the same location. - /// - /// - Parameter receivedNote: A note to record. - /// - Returns: Other notes recorded at the same location. - func recordNote(_ receivedNote: Routeguide_RouteNote) -> [Routeguide_RouteNote] { - return self.notes.withLock { notes in - var notesFromSameLocation: [Routeguide_RouteNote] = [] - for note in notes { - if note.location == receivedNote.location { - notesFromSameLocation.append(note) - } - } - notes.append(receivedNote) - return notesFromSameLocation - } - } - } - - /// Creates a new route guide service. - /// - Parameter features: Known features. - init(features: [Routeguide_Feature]) { - self.features = features - self.receivedNotes = Notes() - } - - /// Returns the first feature found at the given location, if one exists. - private func findFeature(latitude: Int32, longitude: Int32) -> Routeguide_Feature? { - self.features.first { - $0.location.latitude == latitude && $0.location.longitude == longitude - } - } - - func getFeature( - request: ServerRequest.Single - ) async throws -> ServerResponse.Single { - let feature = self.findFeature( - latitude: request.message.latitude, - longitude: request.message.longitude - ) - - if let feature { - return ServerResponse.Single(message: feature) - } else { - // No feature: return a feature with an empty name. - let unknownFeature = Routeguide_Feature.with { - $0.name = "" - $0.location = .with { - $0.latitude = request.message.latitude - $0.longitude = request.message.longitude - } - } - return ServerResponse.Single(message: unknownFeature) - } - } - - func listFeatures( - request: ServerRequest.Single - ) async throws -> ServerResponse.Stream { - return ServerResponse.Stream { writer in - for feature in self.features { - if !feature.name.isEmpty, feature.isContained(by: request.message) { - try await writer.write(feature) - } - } - - return [:] - } - } - - func recordRoute( - request: ServerRequest.Stream - ) async throws -> ServerResponse.Single { - let startTime = ContinuousClock.now - var pointsVisited = 0 - var featuresVisited = 0 - var distanceTravelled = 0.0 - var previousPoint: Routeguide_Point? = nil - - for try await point in request.messages { - pointsVisited += 1 - - if self.findFeature(latitude: point.latitude, longitude: point.longitude) != nil { - featuresVisited += 1 - } - - if let previousPoint { - distanceTravelled += greatCircleDistance(from: previousPoint, to: point) - } - - previousPoint = point - } - - let duration = startTime.duration(to: .now) - let summary = Routeguide_RouteSummary.with { - $0.pointCount = Int32(pointsVisited) - $0.featureCount = Int32(featuresVisited) - $0.elapsedTime = Int32(duration.components.seconds) - $0.distance = Int32(distanceTravelled) - } - - return ServerResponse.Single(message: summary) - } - - func routeChat( - request: ServerRequest.Stream - ) async throws -> ServerResponse.Stream { - return ServerResponse.Stream { writer in - for try await note in request.messages { - let notes = self.receivedNotes.recordNote(note) - try await writer.write(contentsOf: notes) - } - return [:] - } - } -} - -extension Routeguide_Feature { - func isContained(by rectangle: Routeguide_Rectangle) -> Bool { - return rectangle.lo.latitude <= self.location.latitude - && self.location.latitude <= rectangle.hi.latitude - && rectangle.lo.longitude <= self.location.longitude - && self.location.longitude <= rectangle.hi.longitude - } -} - -private func greatCircleDistance( - from point1: Routeguide_Point, - to point2: Routeguide_Point -) -> Double { - // See https://en.wikipedia.org/wiki/Great-circle_distance - // - // Let λ1 (lambda1) and φ1 (phi1) be the longitude and latitude of point 1. - // Let λ2 (lambda2) and φ2 (phi2) be the longitude and latitude of point 2. - // - // Let Δλ = λ2 - λ1, and Δφ = φ2 - φ1. - // - // The central angle between them, σc (sigmaC) can be computed as: - // - // σc = 2 ⨯ sqrt(sin²(Δφ/2) + cos(φ1) ⨯ cos(φ2) ⨯ sin²(Δλ/2)) - // - // The unit distance (d) between point1 and point2 can then be computed as: - // - // d = 2 ⨯ atan(sqrt(σc), sqrt(1 - σc)) - - let lambda1 = radians(degreesInE7: point1.longitude) - let phi1 = radians(degreesInE7: point1.latitude) - let lambda2 = radians(degreesInE7: point2.longitude) - let phi2 = radians(degreesInE7: point2.latitude) - - // Δλ = λ2 - λ1 - let deltaLambda = lambda2 - lambda1 - // Δφ = φ2 - φ1 - let deltaPhi = phi2 - phi1 - - // sin²(Δφ/2) - let sinHalfDeltaPhiSquared = sin(deltaPhi / 2) * sin(deltaPhi / 2) - // sin²(Δλ/2) - let sinHalfDeltaLambdaSquared = sin(deltaLambda / 2) * sin(deltaLambda / 2) - - // σc = 2 ⨯ sqrt(sin²(Δφ/2) + cos(φ1) ⨯ cos(φ2) ⨯ sin²(Δλ/2)) - let sigmaC = 2 * sqrt(sinHalfDeltaPhiSquared + cos(phi1) * cos(phi2) * sinHalfDeltaLambdaSquared) - - // This is the unit distance, i.e. assumes the circle has a radius of 1. - let unitDistance = 2 * atan2(sqrt(sigmaC), sqrt(1 - sigmaC)) - - // Scale it by the radius of the Earth in meters. - let earthRadius = 6_371_000.0 - return unitDistance * earthRadius -} - -private func radians(degreesInE7 degrees: Int32) -> Double { - return (Double(degrees) / 1e7) * .pi / 180.0 -} diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec05-step00-package.swift b/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec05-step00-package.swift deleted file mode 100644 index c9f71095f..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec05-step00-package.swift +++ /dev/null @@ -1,21 +0,0 @@ -// swift-tools-version: 6.0 -import PackageDescription - -let package = Package( - name: "RouteGuide", - platforms: [.macOS(.v15)], - dependencies: [ - .package(url: "https://github.com/grpc/grpc-swift", branch: "main"), - .package(url: "https://github.com/apple/swift-protobuf", from: "1.27.0"), - ], - targets: [ - .executableTarget( - name: "RouteGuide", - dependencies: [ - .product(name: "_GRPCHTTP2Transport", package: "grpc-swift"), - .product(name: "_GRPCProtobuf", package: "grpc-swift"), - .product(name: "SwiftProtobuf", package: "swift-protobuf"), - ] - ) - ] -) diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec05-step01-package.swift b/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec05-step01-package.swift deleted file mode 100644 index 05447cfb9..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec05-step01-package.swift +++ /dev/null @@ -1,24 +0,0 @@ -// swift-tools-version: 6.0 -import PackageDescription - -let package = Package( - name: "RouteGuide", - platforms: [.macOS(.v15)], - dependencies: [ - .package(url: "https://github.com/grpc/grpc-swift", branch: "main"), - .package(url: "https://github.com/apple/swift-protobuf", from: "1.27.0"), - ], - targets: [ - .executableTarget( - name: "RouteGuide", - dependencies: [ - .product(name: "_GRPCHTTP2Transport", package: "grpc-swift"), - .product(name: "_GRPCProtobuf", package: "grpc-swift"), - .product(name: "SwiftProtobuf", package: "swift-protobuf"), - ], - resources: [ - .copy("route_guide_db.json") - ] - ) - ] -) diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec05-step02-package.swift b/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec05-step02-package.swift deleted file mode 100644 index 1a49f098d..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec05-step02-package.swift +++ /dev/null @@ -1,26 +0,0 @@ -// swift-tools-version: 6.0 -import PackageDescription - -let package = Package( - name: "RouteGuide", - platforms: [.macOS(.v15)], - dependencies: [ - .package(url: "https://github.com/grpc/grpc-swift", branch: "main"), - .package(url: "https://github.com/apple/swift-protobuf", from: "1.27.0"), - .package(url: "https://github.com/apple/swift-argument-parser", from: "1.5.0"), - ], - targets: [ - .executableTarget( - name: "RouteGuide", - dependencies: [ - .product(name: "_GRPCHTTP2Transport", package: "grpc-swift"), - .product(name: "_GRPCProtobuf", package: "grpc-swift"), - .product(name: "SwiftProtobuf", package: "swift-protobuf"), - .product(name: "ArgumentParser", package: "swift-argument-parser"), - ], - resources: [ - .copy("route_guide_db.json") - ] - ) - ] -) diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec05-step03-main.swift b/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec05-step03-main.swift deleted file mode 100644 index 07db34cc8..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec05-step03-main.swift +++ /dev/null @@ -1,10 +0,0 @@ -import ArgumentParser - -@main -struct RouteGuide: AsyncParsableCommand { - @Flag - var server: Bool = false - - func run() async throws { - } -} diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec05-step04-load-features.swift b/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec05-step04-load-features.swift deleted file mode 100644 index 82dc23b97..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec05-step04-load-features.swift +++ /dev/null @@ -1,23 +0,0 @@ -import ArgumentParser -import Foundation - -@main -struct RouteGuide: AsyncParsableCommand { - @Flag - var server: Bool = false - - func run() async throws { - if self.server { - try await self.runServer() - } - } - - private static func loadFeatures() throws -> [Routeguide_Feature] { - guard let url = Bundle.module.url(forResource: "route_guide_db", withExtension: "json") else { - throw ExitCode.failure - } - - let data = try Data(contentsOf: url) - return try Routeguide_Feature.array(fromJSONUTF8Bytes: data) - } -} diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec05-step05-run-server.swift b/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec05-step05-run-server.swift deleted file mode 100644 index 6acac0cf8..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec05-step05-run-server.swift +++ /dev/null @@ -1,4 +0,0 @@ -extension RouteGuide { - func runServer() async throws { - } -} diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec05-step06-init-service.swift b/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec05-step06-init-service.swift deleted file mode 100644 index 0aa8f2b54..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec05-step06-init-service.swift +++ /dev/null @@ -1,6 +0,0 @@ -extension RouteGuide { - func runServer() async throws { - let features = try self.loadFeatures() - let routeGuide = RouteGuideService(features: features) - } -} diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec05-step07-server.swift b/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec05-step07-server.swift deleted file mode 100644 index b3ffcaf33..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec05-step07-server.swift +++ /dev/null @@ -1,15 +0,0 @@ -import GRPCHTTP2Transport - -extension RouteGuide { - func runServer() async throws { - let features = try self.loadFeatures() - let routeGuide = RouteGuideService(features: features) - let server = GRPCServer( - transport: .http2NIOPosix( - address: .ipv4(host: "127.0.0.1", port: 31415), - config: .defaults(transportSecurity: .plaintext) - ), - services: [routeGuide] - ) - } -} diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec05-step08-run.swift b/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec05-step08-run.swift deleted file mode 100644 index b99885335..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec05-step08-run.swift +++ /dev/null @@ -1,25 +0,0 @@ -import GRPCHTTP2Transport - -extension RouteGuide { - func runServer() async throws { - let features = try self.loadFeatures() - let routeGuide = RouteGuideService(features: features) - let server = GRPCServer( - transport: .http2NIOPosix( - address: .ipv4(host: "127.0.0.1", port: 31415), - config: .defaults(transportSecurity: .plaintext) - ), - services: [routeGuide] - ) - - try await withThrowingTaskGroup(of: Void.self) { group in - group.addTask { - try await server.serve() - } - - if let address = try await server.listeningAddress { - print("RouteGuide server listening on \(address)") - } - } - } -} diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec06-step01-call-run-client.swift b/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec06-step01-call-run-client.swift deleted file mode 100644 index 96c6111db..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec06-step01-call-run-client.swift +++ /dev/null @@ -1,25 +0,0 @@ -import ArgumentParser -import Foundation - -@main -struct RouteGuide: AsyncParsableCommand { - @Flag - var server: Bool = false - - func run() async throws { - if self.server { - try await self.runServer() - } else { - try await self.runClient() - } - } - - private static func loadFeatures() throws -> [Routeguide_Feature] { - guard let url = Bundle.module.url(forResource: "route_guide_db", withExtension: "json") else { - throw ExitCode.failure - } - - let data = try Data(contentsOf: url) - return try Routeguide_Feature.array(fromJSONUTF8Bytes: data) - } -} diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec06-step02-add-run-client.swift b/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec06-step02-add-run-client.swift deleted file mode 100644 index eee2b6838..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec06-step02-add-run-client.swift +++ /dev/null @@ -1,6 +0,0 @@ -import GRPCHTTP2Transport - -extension RouteGuide { - func runClient() async throws { - } -} diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec06-step03-create-client.swift b/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec06-step03-create-client.swift deleted file mode 100644 index ac97861b9..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec06-step03-create-client.swift +++ /dev/null @@ -1,12 +0,0 @@ -import GRPCHTTP2Transport - -extension RouteGuide { - func runClient() async throws { - let client = try GRPCClient( - transport: .http2NIOPosix( - target: .ipv4(host: "127.0.0.1", port: 31415), - config: .defaults() - ) - ) - } -} diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec06-step04-run-client.swift b/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec06-step04-run-client.swift deleted file mode 100644 index 76332bada..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec06-step04-run-client.swift +++ /dev/null @@ -1,14 +0,0 @@ -import GRPCHTTP2Transport - -extension RouteGuide { - func runClient() async throws { - let client = try GRPCClient( - transport: .http2NIOPosix( - target: .ipv4(host: "127.0.0.1", port: 31415), - config: .defaults() - ) - ) - - async let _ = client.run() - } -} diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec06-step05-stub.swift b/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec06-step05-stub.swift deleted file mode 100644 index e231432bc..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec06-step05-stub.swift +++ /dev/null @@ -1,16 +0,0 @@ -import GRPCHTTP2Transport - -extension RouteGuide { - func runClient() async throws { - let client = try GRPCClient( - transport: .http2NIOPosix( - target: .ipv4(host: "127.0.0.1", port: 31415), - config: .defaults() - ) - ) - - async let _ = client.run() - - let routeGuide = Routeguide_RouteGuideClient(wrapping: client) - } -} diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec06-step06-get-feature.swift b/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec06-step06-get-feature.swift deleted file mode 100644 index 5d4e5e725..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec06-step06-get-feature.swift +++ /dev/null @@ -1,29 +0,0 @@ -import GRPCHTTP2Transport - -extension RouteGuide { - func runClient() async throws { - let client = try GRPCClient( - transport: .http2NIOPosix( - target: .ipv4(host: "127.0.0.1", port: 31415), - config: .defaults() - ) - ) - - async let _ = client.run() - - let routeGuide = Routeguide_RouteGuideClient(wrapping: client) - try await self.getFeature(using: routeGuide) - } - - private func getFeature(using routeGuide: Routeguide_RouteGuideClient) async throws { - print("→ Calling 'GetFeature'") - - let point = Routeguide_Point.with { - $0.latitude = 407_838_351 - $0.longitude = -746_143_763 - } - - let feature = try await routeGuide.getFeature(point) - print("Got feature '\(feature.name)'") - } -} diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec06-step07-list-features.swift b/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec06-step07-list-features.swift deleted file mode 100644 index d22daa343..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec06-step07-list-features.swift +++ /dev/null @@ -1,53 +0,0 @@ -import GRPCHTTP2Transport - -extension RouteGuide { - func runClient() async throws { - let client = try GRPCClient( - transport: .http2NIOPosix( - target: .ipv4(host: "127.0.0.1", port: 31415), - config: .defaults() - ) - ) - - async let _ = client.run() - - let routeGuide = Routeguide_RouteGuideClient(wrapping: client) - try await self.getFeature(using: routeGuide) - try await self.listFeatures(using: routeGuide) - } - - private func getFeature(using routeGuide: Routeguide_RouteGuideClient) async throws { - print("→ Calling 'GetFeature'") - - let point = Routeguide_Point.with { - $0.latitude = 407_838_351 - $0.longitude = -746_143_763 - } - - let feature = try await routeGuide.getFeature(point) - print("Got feature '\(feature.name)'") - } - - private func listFeatures(using routeGuide: Routeguide_RouteGuideClient) async throws { - print("→ Calling 'ListFeatures'") - - let boundingRectangle = Routeguide_Rectangle.with { - $0.lo = .with { - $0.latitude = 400_000_000 - $0.longitude = -750_000_000 - } - $0.hi = .with { - $0.latitude = 420_000_000 - $0.longitude = -730_000_000 - } - } - - try await routeGuide.listFeatures(boundingRectangle) { response in - for try await feature in response.messages { - print( - "Got feature '\(feature.name)' at (\(feature.location.latitude), \(feature.location.longitude))" - ) - } - } - } -} diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec06-step08-record-route.swift b/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec06-step08-record-route.swift deleted file mode 100644 index 6b5ecaba7..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec06-step08-record-route.swift +++ /dev/null @@ -1,76 +0,0 @@ -import GRPCHTTP2Transport - -extension RouteGuide { - func runClient() async throws { - let client = try GRPCClient( - transport: .http2NIOPosix( - target: .ipv4(host: "127.0.0.1", port: 31415), - config: .defaults() - ) - ) - - async let _ = client.run() - - let routeGuide = Routeguide_RouteGuideClient(wrapping: client) - try await self.getFeature(using: routeGuide) - try await self.listFeatures(using: routeGuide) - try await self.recordRoute(using: routeGuide) - } - - private func getFeature(using routeGuide: Routeguide_RouteGuideClient) async throws { - print("→ Calling 'GetFeature'") - - let point = Routeguide_Point.with { - $0.latitude = 407_838_351 - $0.longitude = -746_143_763 - } - - let feature = try await routeGuide.getFeature(point) - print("Got feature '\(feature.name)'") - } - - private func listFeatures(using routeGuide: Routeguide_RouteGuideClient) async throws { - print("→ Calling 'ListFeatures'") - - let boundingRectangle = Routeguide_Rectangle.with { - $0.lo = .with { - $0.latitude = 400_000_000 - $0.longitude = -750_000_000 - } - $0.hi = .with { - $0.latitude = 420_000_000 - $0.longitude = -730_000_000 - } - } - - try await routeGuide.listFeatures(boundingRectangle) { response in - for try await feature in response.messages { - print( - "Got feature '\(feature.name)' at (\(feature.location.latitude), \(feature.location.longitude))" - ) - } - } - } - - private func recordRoute(using routeGuide: Routeguide_RouteGuideClient) async throws { - print("→ Calling 'RecordRoute'") - - let features = try self.loadFeatures() - let pointsToVisit = 10 - - let summary = try await routeGuide.recordRoute { writer in - for _ in 0 ..< pointsToVisit { - if let feature = features.randomElement() { - try await writer.write(feature.location) - } - } - } - - print( - """ - Finished trip with \(summary.pointCount) points. Passed \(summary.featureCount) \ - features. Travelled \(summary.distance) meters. It took \(summary.elapsedTime) seconds. - """ - ) - } -} diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec06-step09-route-chat.swift b/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec06-step09-route-chat.swift deleted file mode 100644 index a0fa49c55..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec06-step09-route-chat.swift +++ /dev/null @@ -1,107 +0,0 @@ -import GRPCHTTP2Transport - -extension RouteGuide { - func runClient() async throws { - let client = try GRPCClient( - transport: .http2NIOPosix( - target: .ipv4(host: "127.0.0.1", port: 31415), - config: .defaults() - ) - ) - - async let _ = client.run() - - let routeGuide = Routeguide_RouteGuideClient(wrapping: client) - try await self.getFeature(using: routeGuide) - try await self.listFeatures(using: routeGuide) - try await self.recordRoute(using: routeGuide) - try await self.routeChat(using: routeGuide) - } - - private func getFeature(using routeGuide: Routeguide_RouteGuideClient) async throws { - print("→ Calling 'GetFeature'") - - let point = Routeguide_Point.with { - $0.latitude = 407_838_351 - $0.longitude = -746_143_763 - } - - let feature = try await routeGuide.getFeature(point) - print("Got feature '\(feature.name)'") - } - - private func listFeatures(using routeGuide: Routeguide_RouteGuideClient) async throws { - print("→ Calling 'ListFeatures'") - - let boundingRectangle = Routeguide_Rectangle.with { - $0.lo = .with { - $0.latitude = 400_000_000 - $0.longitude = -750_000_000 - } - $0.hi = .with { - $0.latitude = 420_000_000 - $0.longitude = -730_000_000 - } - } - - try await routeGuide.listFeatures(boundingRectangle) { response in - for try await feature in response.messages { - print( - "Got feature '\(feature.name)' at (\(feature.location.latitude), \(feature.location.longitude))" - ) - } - } - } - - private func recordRoute(using routeGuide: Routeguide_RouteGuideClient) async throws { - print("→ Calling 'RecordRoute'") - - let features = try self.loadFeatures() - let pointsToVisit = 10 - - let summary = try await routeGuide.recordRoute { writer in - for _ in 0 ..< pointsToVisit { - if let feature = features.randomElement() { - try await writer.write(feature.location) - } - } - } - - print( - """ - Finished trip with \(summary.pointCount) points. Passed \(summary.featureCount) \ - features. Travelled \(summary.distance) meters. It took \(summary.elapsedTime) seconds. - """ - ) - } - - private func routeChat(using routeGuide: Routeguide_RouteGuideClient) async throws { - print("→ Calling 'RouteChat'") - - try await routeGuide.routeChat { writer in - let notes: [(String, (Int32, Int32))] = [ - ("First message", (0, 0)), - ("Second message", (0, 1)), - ("Third message", (1, 0)), - ("Fourth message", (1, 1)), - ("Fifth message", (0, 0)), - ] - - for (message, (lat, lon)) in notes { - let note = Routeguide_RouteNote.with { - $0.message = message - $0.location.latitude = lat - $0.location.longitude = lon - } - print("Sending message '\(message)' at (\(lat), \(lon))") - try await writer.write(note) - } - } onResponse: { response in - for try await note in response.messages { - print( - "Got message '\(note.message)' at (\(note.location.latitude), \(note.location.longitude))" - ) - } - } - } -} diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec07-step01-server.txt b/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec07-step01-server.txt deleted file mode 100644 index 4056a8e5c..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec07-step01-server.txt +++ /dev/null @@ -1 +0,0 @@ -RouteGuide server listening on [ipv4]127.0.0.1:31415 diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec07-step02-client.txt b/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec07-step02-client.txt deleted file mode 100644 index def58639d..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Resources/route-guide-sec07-step02-client.txt +++ /dev/null @@ -1,76 +0,0 @@ -→ Calling 'GetFeature' -Got feature 'Patriots Path, Mendham, NJ 07945, USA' -→ Calling 'ListFeatures' -Got feature 'Patriots Path, Mendham, NJ 07945, USA' at (407838351, -746143763) -Got feature '101 New Jersey 10, Whippany, NJ 07981, USA' at (408122808, -743999179) -Got feature 'U.S. 6, Shohola, PA 18458, USA' at (413628156, -749015468) -Got feature '5 Conners Road, Kingston, NY 12401, USA' at (419999544, -740371136) -Got feature 'Mid Hudson Psychiatric Center, New Hampton, NY 10958, USA' at (414008389, -743951297) -Got feature '287 Flugertown Road, Livingston Manor, NY 12758, USA' at (419611318, -746524769) -Got feature '4001 Tremley Point Road, Linden, NJ 07036, USA' at (406109563, -742186778) -Got feature '352 South Mountain Road, Wallkill, NY 12589, USA' at (416802456, -742370183) -Got feature 'Bailey Turn Road, Harriman, NY 10926, USA' at (412950425, -741077389) -Got feature '193-199 Wawayanda Road, Hewitt, NJ 07421, USA' at (412144655, -743949739) -Got feature '406-496 Ward Avenue, Pine Bush, NY 12566, USA' at (415736605, -742847522) -Got feature '162 Merrill Road, Highland Mills, NY 10930, USA' at (413843930, -740501726) -Got feature 'Clinton Road, West Milford, NJ 07480, USA' at (410873075, -744459023) -Got feature '16 Old Brook Lane, Warwick, NY 10990, USA' at (412346009, -744026814) -Got feature '3 Drake Lane, Pennington, NJ 08534, USA' at (402948455, -747903913) -Got feature '6324 8th Avenue, Brooklyn, NY 11220, USA' at (406337092, -740122226) -Got feature '1 Merck Access Road, Whitehouse Station, NJ 08889, USA' at (406421967, -747727624) -Got feature '78-98 Schalck Road, Narrowsburg, NY 12764, USA' at (416318082, -749677716) -Got feature '282 Lakeview Drive Road, Highland Lake, NY 12743, USA' at (415301720, -748416257) -Got feature '330 Evelyn Avenue, Hamilton Township, NJ 08619, USA' at (402647019, -747071791) -Got feature 'New York State Reference Route 987E, Southfields, NY 10975, USA' at (412567807, -741058078) -Got feature '103-271 Tempaloni Road, Ellenville, NY 12428, USA' at (416855156, -744420597) -Got feature '1300 Airport Road, North Brunswick Township, NJ 08902, USA' at (404663628, -744820157) -Got feature '211-225 Plains Road, Augusta, NJ 07822, USA' at (411633782, -746784970) -Got feature '165 Pedersen Ridge Road, Milford, PA 18337, USA' at (413447164, -748712898) -Got feature '100-122 Locktown Road, Frenchtown, NJ 08825, USA' at (405047245, -749800722) -Got feature '650-652 Willi Hill Road, Swan Lake, NY 12783, USA' at (417951888, -748484944) -Got feature '26 East 3rd Street, New Providence, NJ 07974, USA' at (407033786, -743977337) -Got feature '611 Lawrence Avenue, Westfield, NJ 07090, USA' at (406589790, -743560121) -Got feature '18 Lannis Avenue, New Windsor, NY 12553, USA' at (414653148, -740477477) -Got feature '82-104 Amherst Avenue, Colonia, NJ 07067, USA' at (405957808, -743255336) -Got feature '170 Seven Lakes Drive, Sloatsburg, NY 10974, USA' at (411733589, -741648093) -Got feature '1270 Lakes Road, Monroe, NY 10950, USA' at (412676291, -742606606) -Got feature '509-535 Alphano Road, Great Meadows, NJ 07838, USA' at (409224445, -748286738) -Got feature '652 Garden Street, Elizabeth, NJ 07202, USA' at (406523420, -742135517) -Got feature '349 Sea Spray Court, Neptune City, NJ 07753, USA' at (401827388, -740294537) -Got feature '13-17 Stanley Street, West Milford, NJ 07480, USA' at (410564152, -743685054) -Got feature '47 Industrial Avenue, Teterboro, NJ 07608, USA' at (408472324, -740726046) -Got feature '5 White Oak Lane, Stony Point, NY 10980, USA' at (412452168, -740214052) -Got feature 'Berkshire Valley Management Area Trail, Jefferson, NJ, USA' at (409146138, -746188906) -Got feature '1007 Jersey Avenue, New Brunswick, NJ 08901, USA' at (404701380, -744781745) -Got feature '6 East Emerald Isle Drive, Lake Hopatcong, NJ 07849, USA' at (409642566, -746017679) -Got feature '1358-1474 New Jersey 57, Port Murray, NJ 07865, USA' at (408031728, -748645385) -Got feature '367 Prospect Road, Chester, NY 10918, USA' at (413700272, -742135189) -Got feature '10 Simon Lake Drive, Atlantic Highlands, NJ 07716, USA' at (404310607, -740282632) -Got feature '11 Ward Street, Mount Arlington, NJ 07856, USA' at (409319800, -746201391) -Got feature '300-398 Jefferson Avenue, Elizabeth, NJ 07201, USA' at (406685311, -742108603) -Got feature '43 Dreher Road, Roscoe, NY 12776, USA' at (419018117, -749142781) -Got feature 'Swan Street, Pine Island, NY 10969, USA' at (412856162, -745148837) -Got feature '66 Pleasantview Avenue, Monticello, NY 12701, USA' at (416560744, -746721964) -Got feature '565 Winding Hills Road, Montgomery, NY 12549, USA' at (415534177, -742900616) -Got feature '231 Rocky Run Road, Glen Gardner, NJ 08826, USA' at (406898530, -749127080) -Got feature '100 Mount Pleasant Avenue, Newark, NJ 07104, USA' at (407586880, -741670168) -Got feature '517-521 Huntington Drive, Manchester Township, NJ 08759, USA' at (400106455, -742870190) -Got feature '40 Mountain Road, Napanoch, NY 12458, USA' at (418803880, -744102673) -Got feature '48 North Road, Forestburgh, NY 12777, USA' at (415464475, -747175374) -Got feature '9 Thompson Avenue, Leonardo, NJ 07737, USA' at (404226644, -740517141) -Got feature '213 Bush Road, Stone Ridge, NY 12484, USA' at (418811433, -741718005) -Got feature '1-17 Bergen Court, New Brunswick, NJ 08901, USA' at (404839914, -744759616) -Got feature '35 Oakland Valley Road, Cuddebackville, NY 12729, USA' at (414638017, -745957854) -Got feature '42-102 Main Street, Belford, NJ 07718, USA' at (404318328, -740835638) -Got feature '3387 Richmond Terrace, Staten Island, NY 10303, USA' at (406411633, -741722051) -Got feature '261 Van Sickle Road, Goshen, NY 10924, USA' at (413069058, -744597778) -Got feature '3 Hasta Way, Newton, NJ 07860, USA' at (410248224, -747127767) -→ Calling 'RecordRoute' -Finished trip with 10 points. Passed "10 features. Travelled 10314218 meters. It took 0 seconds. -→ Calling 'RouteChat' -Sending message 'First message' at (0, 0) -Sending message 'Second message' at (0, 1) -Sending message 'Third message' at (1, 0) -Sending message 'Fourth message' at (1, 1) -Sending message 'Fifth message' at (0, 0) -Got message 'First message' at (0, 0) diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Route-Guide.tutorial b/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Route-Guide.tutorial deleted file mode 100644 index 52b404049..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Route-Guide/Route-Guide.tutorial +++ /dev/null @@ -1,500 +0,0 @@ -@Tutorial(time: 30) { - @XcodeRequirement( - title: "Xcode 16 Beta 6+", - destination: "https://developer.apple.com/download/" - ) - - @Intro(title: "The Basics: Route Guide") { - Follow this tutorial to learn how to create a gRPC service and client from scratch. You'll - learn how to define a service in a `.proto` file, generate server and client code using the - Protocol Buffer compiler, and use gRPC Swift to write a simple client and server for your - service. - - This tutorial assumes that you have read the [Overview](https://grpc.io/docs) and are familiar - with [Protocol Buffers](https://developers.google.com/protocol-buffers/docs/proto3). - - If you're looking for a simpler starting point, try the tutorial. - } - - @Section(title: "Setup") { - Before we can write any code we need to create a new Swift Package and configure it - to depend on gRPC Swift. - - @Steps { - @Step { - Create a new directory called for the package called `RouteGuide`. - - @Code(name: "Console.txt", file: "route-guide-sec01-step01-mkdir.txt") - } - - @Step { - All subsequent commands used in this tutorial assume that you're working from the directory - you just created so change to it now. - - @Code(name: "Console.txt", file: "route-guide-sec01-step02-cd.txt") - } - - @Step { - We need to create a few more directories, one for our source code and another for the - Protocol Buffers files. - - @Code(name: "Console.txt", file: "route-guide-sec01-step03-mkdir.txt") - } - - @Step { - Now we'll create a manifest for our Swift Package. Create a new empty file - called `Package.swift`. - - The first line in the file is a directive indicating what tools version is required to build - the package. For gRPC Swift v2, the tools version must be 6.0 or newer. - - @Code(name: "Package.swift", file: "route-guide-sec01-step04-tools-version.swift") - } - - @Step { - Import the `PackageDescription` module so we can describe our package. - - @Code(name: "Package.swift", file: "route-guide-sec01-step05-import.swift") - } - - @Step { - Create an empty package object called `RouteGuide`. - - @Code(name: "Package.swift", file: "route-guide-sec01-step06-description.swift") - } - - @Step { - We need to add a dependency on the gRPC Swift and Swift Protobuf packages. As gRPC Swift v2 - hasn't yet been released the dependency must be on the `main` branch. - - Note that we also add `.macOS(.v15)` to platforms, this is the earliest macOS version supported by - gRPC Swift v2. - - @Code(name: "Package.swift", file: "route-guide-sec01-step07-description.swift") - } - - @Step { - Next we can add a target. In this tutorial we'll create a single executable target which - can act as both a client and a server. - - We require two gRPC dependencies: `_GRPCHTTP2Transport"` provides an implementation of an HTTP/2 - client and server, while `_GRPCProtobuf` provides the necessary components to serialize - and deserialize `SwiftProtobuf` messages. - - > Because gRPC Swift v2 hasn't been released yet the product names are prefixed - > with an `"_"`; this indicates that they aren't yet considered as stable public API. - - @Code(name: "Package.swift", file: "route-guide-sec01-step08-description.swift") - } - } - } - - @Section(title: "Defining the service") { - Our next step is to define our gRPC *service* and its *request* and *response* types using - Protocol Buffers. - - @Steps { - @Step { - Create a new empty file in the `Protos` directory called `route_guide.proto`. We'll use - the "proto3" syntax and our service will be part of the "routeguide" package. - - @Code(name: "Protos/route_guide.proto", file: "route-guide-sec02-step01-import.proto") - } - - @Step { - To define a service we create a named `service` in the `.proto` file. - - @Code(name: "Protos/route_guide.proto", file: "route-guide-sec02-step02-service.proto") - } - - @Step { - Then we define `rpc` methods inside our service definition, specifying their - request and response types. gRPC lets you define four kinds of service methods, - all of which are used in the `RouteGuide` service. - - A *unary RPC* where the client sends a request to the server using the stub - and waits for a response to come back, just like a normal function call. - - @Code(name: "Protos/route_guide.proto", file: "route-guide-sec02-step03-unary.proto") - } - - @Step { - A *server-side streaming RPC* where the client sends a request to the server - and gets a stream to read a sequence of messages back. The client reads from - the returned stream until there are no more messages. As you can see in our - example, you specify a server-side streaming method by placing the `stream` - keyword before the *response* type. - - @Code(name: "Protos/route_guide.proto", file: "route-guide-sec02-step04-server-streaming.proto") - } - - @Step { - A *client-side streaming RPC* where the client writes a sequence of messages - and sends them to the server, again using a provided stream. Once the client - has finished writing the messages, it waits for the server to read them all - and return its response. You specify a client-side streaming method by placing - the `stream` keyword before the *request* type. - - @Code(name: "Protos/route_guide.proto", file: "route-guide-sec02-step05-client-streaming.proto") - } - - @Step { - A *bidirectional streaming RPC* where both sides send a sequence of messages - using a read-write stream. The two streams operate independently, so clients - and servers can read and write in whatever order they like: for example, the - server could wait to receive all the client messages before writing its - responses, or it could alternately read a message then write a message, or - some other combination of reads and writes. The order of messages in each - stream is preserved. You specify this type of method by placing the `stream` - keyword before both the request and the response. - - @Code(name: "Protos/route_guide.proto", file: "route-guide-sec02-step06-bidi-streaming.proto") - } - - @Step { - The `.proto` file also contains the Protocol Buffers message type definitions for all - request and response messages used by the service. - - @Code(name: "Protos/route_guide.proto", file: "route-guide-sec02-step07-messages.proto") - } - } - } - - @Section(title: "Generating client and server code") { - Next we need to generate the gRPC client and server interfaces from our `.proto` - service definition. We do this using the Protocol Buffer compiler, `protoc`, with - two plugins: one with support for Swift (via [Swift Protobuf](https://github.com/apple/swift-protobuf)) - and the other for gRPC. This section assumes you already have `protoc` installed. - - To learn more about generating code check out the article. - - @Steps { - @Step { - First we need to build the two plugins for `protoc`, `protoc-gen-swift` and - `protoc-gen-grpc-swift`. - - @Code(name: "Console", file: "route-guide-sec03-step01-protoc-plugins.txt") - } - - @Step { - We'll generate the code into a separate directory within `Sources` called `Generated` which - we need to create first. - - @Code(name: "Console", file: "route-guide-sec03-step02-mkdir.txt") - } - - @Step { - Now run `protoc` to generate the messages. This will create - `Sources/Generated/route_guide.pb.swift`. - - @Code(name: "Console", file: "route-guide-sec03-step03-gen-messages.txt") - } - - @Step { - Run `protoc` again to generate the service code. This will create - `Sources/Generated/route_guide.grpc.swift`. - - > `protoc-gen-grpc-swift` is currently shared with v1 so the `_V2=true` option is required. - > This will be removed when v2 is released. - - @Code(name: "Console", file: "route-guide-sec03-step04-gen-grpc.txt") - } - } - } - - @Section(title: "Creating the service") { - Now that we've generated the service stubs and messages we can create a server. If you're only - interested in creating gRPC clients you can skip this section (although you might find it - interesting anyway!) - - There are two parts to making our service do its job: - 1. Implementing the service protocol generated from our service definition, doing the - actual "work" of our service. - 2. Running a gRPC server to listen for requests from clients and return the service responses. - - This section explains how to implement the service protocol. - - @Steps { - @Step { - Create a new empty file in `Sources` called `RouteGuideService.swift`. To implement - the service we need to conform a type to the generated service protocol. The name - of the protocol will be `_.ServiceProtocol` where `` and - `` are both taken from the `.proto` file. - - Create a `struct` called `RouteGuideService` which conforms to - the `Routeguide_RouteGuide.ServiceProtocol`. - - @Code(name: "Sources/RouteGuideService.swift", file: "route-guide-sec04-step01-struct.swift") - } - - @Step { - The compiler will produce an error because the requirements of the protocol haven't yet been - met. It should also suggest a fix-it to generate the methods for you. Apply it so that you - get empty function definitions. They should look like this: - - @Code(name: "Sources/RouteGuideService.swift", file: "route-guide-sec04-step02-unimplemented.swift") - } - - @Step { - To implement the unary `getFeature` method the service needs a way to get features. We'll - just store the features in an array which is passed to the service when it's initialized. - - @Code(name: "Sources/RouteGuideService.swift", file: "route-guide-sec04-step03-features.swift") - } - - @Step { - `GetFeature` is a unary RPC which takes a single point as input and returns a single - feature back to the client. Its generated method, `getFeature`, has one parameter: - `ServerRequest.Single` describing the request. To return our response to - the client and complete the call we must first lookup a feature at the given point. - - @Code(name: "Sources/RouteGuideService.swift", file: "route-guide-sec04-step04-unary.swift") - } - - @Step { - Then create and return an appropriate `ServerResponse.Single` to the - client. - - @Code(name: "Sources/RouteGuideService.swift", file: "route-guide-sec04-step05-unary.swift") - } - - @Step { - Next, let's look at one of our streaming RPCs. Like the unary RPC, this method gets a - request object, `ServerRequest.Single`, which has a message describing - the area in which the client wants to list features. As this is a server-side streaming RPC - we can send back multiple `Routeguide_Feature` messages to our client. - - To implement the method we must return a `ServerResponse.Stream` which is initialized with - a closure to produce messages. The closure is passed a writer allowing you to write back - messages. We can write back a message for each feature we find in the rectangle. - - @Code(name: "Sources/RouteGuideService.swift", file: "route-guide-sec04-step06-server-streaming.swift") - } - - @Step { - You can also send metadata to the client once the RPC has finished, in this case we don't - have any to send back so return the empty `Metadata` collection. - - @Code(name: "Sources/RouteGuideService.swift", file: "route-guide-sec04-step07-server-streaming.swift") - } - - @Step { - Now let's look at something a little more complicated: the client-side streaming - method `RecordRoute`, where we get a stream of `Routeguide_Point`s from the client and - return a single `Routeguide_RouteSummary` with information about their trip. - - As you can see our method gets a `ServerRequest.Stream` parameter and - returns a `ServerResponse.Single`. In the method we iterate over - the asynchronous stream of points sent by the client. For each point we check if there's - a feature at that point and calculate the distance between that and the last point we saw. - After the *client* has finished sending points we populate a `Routeguide_RouteSummary` which - we return in the response. - - @Code(name: "Sources/RouteGuideService.swift", file: "route-guide-sec04-step08-client-streaming.swift") - } - - @Step { - Finally, let's look at our bidirectional streaming RPC `RouteChat`. Here we receive a stream - of `Routeguide_RouteNote`s and return a stream of `Routeguide_RouteNote`s. For this RPC we - also need to modify the state of our service to store notes sent by the client. We'll do - this with a helper class to store the notes. - - @Code(name: "Sources/RouteGuideService.swift", file: "route-guide-sec04-step09-bidi-streaming.swift") - } - - @Step { - To implement the RPC we return a `ServerResponse.Stream`. Like in the - server-side streaming RPC it's initialized with a closure for writing back messages. In - the body of the closure we iterate the request messages and for each one call our helper - class to record the note and get all other notes recorded in the same location. We then - write each of those notes back to the client. - - @Code(name: "Sources/RouteGuideService.swift", file: "route-guide-sec04-step10-bidi-streaming.swift") - } - } - } - - @Section(title: "Creating the server") { - Now that we've created the service we can create a server. - - @Steps { - @Step { - First we need to download a database of known features for the service. Copy - `route_guide_db.json` from the [gRPC Swift repository](https://github.com/grpc/grpc-swift/tree/main/Examples/v2/route-guide) - and place it in the `Sources` directory. - } - - @Step { - Next, we need to add it as a resource to the target. Open `Package.swift`. - - @Code(name: "Package.swift", file: "route-guide-sec05-step00-package.swift") - } - - @Step { - Now add a `resouces` argument to copy the `route_guide_db.json` database. - - @Code(name: "Package.swift", file: "route-guide-sec05-step01-package.swift") - } - - @Step { - We'll also need to add a dependency on Swift Argument Parser to the package and target. - - @Code(name: "Package.swift", file: "route-guide-sec05-step02-package.swift") - } - - @Step { - With that done we can now create a new file in `Sources` called `RouteGuide.swift` with the - following code in. - - `@main` indicates that the type contains the entry point to the program. In this case, - because `RouteGuide` conforms to `AsyncParseableCommand`, the entry point to our program - is the `run()` method. The `@Flag` annotation marks `server` as a flag to the argument - parser so that you can specify `--server` when running the program. - - @Code(name: "Sources/RouteGuide.swift", file: "route-guide-sec05-step03-main.swift") - } - - @Step { - We'll also add a helper to load the features from the resource we added in the package - manifest. Let's call `runServer` if the `server` flag is set. We'll define - the `runServer` method next. - - @Code(name: "Sources/RouteGuide.swift", file: "route-guide-sec05-step04-load-features.swift") - } - - @Step { - Create a new file in `Sources` called `RouteGuide+Server.swift`, we'll use this for all of - the server specific code. - - @Code(name: "Sources/RouteGuide+Server.swift", file: "route-guide-sec05-step05-run-server.swift") - } - - @Step { - Next, let's load the features and instantiate our service. - - @Code(name: "Sources/RouteGuide+Server.swift", file: "route-guide-sec05-step06-init-service.swift") - } - - @Step { - The server is instantiated with a transport which provides the communication with a remote - peer. - - We'll use an HTTP/2 transport provided by the `GRPCHTTP2Transport` module which is - implemented on top of SwiftNIO's Posix sockets abstraction. The server is configured to - bind to "127.0.0.1:31415" and use the default configuration. The `.plaintext` transport - security means that the connections won't use TLS and will be unencrypted. - - @Code(name: "Sources/RouteGuide+Server.swift", file: "route-guide-sec05-step07-server.swift") - } - - @Step { - Finally, we start the server and then print out its listening address when it's started. - - @Code(name: "Sources/RouteGuide+Server.swift", file: "route-guide-sec05-step08-run.swift") - } - } - } - - @Section(title: "Creating the client") { - In this section, we'll look at creating a Swift client for our `RouteGuide` service. - - To call service methods, we first need to create a *stub*. All generated Swift stubs - are *asynchronous*. - - @Steps { - @Step { - Like for the server we'll add a method to run the client RPCs. Open `RouteGuide.swift` - again and add a call to `runClient`. - - @Code(name: "Sources/RouteGuide+Client.swift", file: "route-guide-sec06-step01-call-run-client.swift") - } - - @Step { - Create a new file in `Sources` called `RouteGuide+Client.swift` and define the - `runClient` function. - - @Code(name: "Sources/RouteGuide+Client.swift", file: "route-guide-sec06-step02-add-run-client.swift") - } - - @Step { - First we need to create a gRPC client for our stub, we're not using TLS so we - use the `.plaintext` security transport and specify the server address and port - we want to connect to. - - @Code(name: "Sources/RouteGuide+Client.swift", file: "route-guide-sec06-step03-create-client.swift") - } - - @Step { - To make RPCs using the client it needs to be running. Running the client will resolve the - target address, start a load balancer and then maintain connections to the server. - - @Code(name: "Sources/RouteGuide+Client.swift", file: "route-guide-sec06-step04-run-client.swift") - } - - @Step { - We can now instantiate the stub with our `client` and call service methods. - - @Code(name: "Sources/RouteGuide+Client.swift", file: "route-guide-sec06-step05-stub.swift") - } - - @Step { - Calling the unary RPC `GetFeature` is straightforward, we create a - `Routeguide_Point` and pass it to the `getFeature` method and it returns - a `Routeguide_Feature`. - - @Code(name: "Sources/RouteGuide+Client.swift", file: "route-guide-sec06-step06-get-feature.swift") - } - - @Step { - Next, let's look at a server-side streaming call to `ListFeatures` which returns a stream of - features within a bounding rectangle. It's very similar to the unary RPC we just looked at - except that we must pass a response handling closure to `listFeatures`. Inside the closure - we can iterate the stream of features returned from the server. - - The response handling closure makes the lifetime of the RPC clear: only once the closure - returns does the `listFeature` return, at which point any resources used by gRPC to execute - the RPC will have been cleaned up. - - @Code(name: "Sources/RouteGuide+Client.swift", file: "route-guide-sec06-step07-list-features.swift") - } - - @Step { - Now for something a little more complicated: the client-side streaming method `RecordRoute`, - where we send a stream of `Routeguide_Point`s to the server and get back a - single `Routeguide_RouteSummary`. The `recordRoute` method takes a closure which is passed - a writer which we can use to send messages to the server. Once the closure returns the - server knows that the client is done sending messages and can return a summary to the - client. - - @Code(name: "Sources/RouteGuide+Client.swift", file: "route-guide-sec06-step08-record-route.swift") - } - - @Step { - Finally, let's look at our bidirectional stream `RouteChat` RPC. As with our client-side - streaming example, we have a closure for writing notes, and like the server-side streaming - example another closure to handle the response. - - @Code(name: "Sources/RouteGuide+Client.swift", file: "route-guide-sec06-step09-route-chat.swift") - } - } - } - - @Section(title: "Try it out!") { - Now that you've implemented the service and created a client to call the various RPCs - we can try running it. - - @Steps { - @Step { - In one terminal run `swift run RouteGuide --server` to start the server. - - @Code(name: "Console", file: "route-guide-sec07-step01-server.txt") - } - - @Step { - In another terminal run `swift run RouteGuide` to run the client program. - - @Code(name: "Console", file: "route-guide-sec07-step02-client.txt") - } - } - } -} diff --git a/Sources/GRPCCore/Documentation.docc/Tutorials/Table-of-Contents.tutorial b/Sources/GRPCCore/Documentation.docc/Tutorials/Table-of-Contents.tutorial deleted file mode 100644 index 076e13c14..000000000 --- a/Sources/GRPCCore/Documentation.docc/Tutorials/Table-of-Contents.tutorial +++ /dev/null @@ -1,21 +0,0 @@ -@Tutorials(name: "gRPC Swift") { - @Intro(title: "Working with gRPC Swift") { - Learn how to use gRPC Swift to implement gRPC services and call them using generated clients. - } - - @Chapter(name: "Quick Start") { - Follow this "Hello World" tutorial for a quick start with gRPC Swift. - - @Image(source: "image.png") - - @TutorialReference(tutorial: "doc:Hello-World") - } - - @Chapter(name: "The Basics") { - Follow the basic tutorial to learn how to create a gRPC service and client from scratch. - - @Image(source: "image.png") - - @TutorialReference(tutorial: "doc:Route-Guide") - } -} diff --git a/Sources/GRPCCore/GRPCClient.swift b/Sources/GRPCCore/GRPCClient.swift deleted file mode 100644 index 6c2729548..000000000 --- a/Sources/GRPCCore/GRPCClient.swift +++ /dev/null @@ -1,377 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -private import Synchronization - -/// A gRPC client. -/// -/// A ``GRPCClient`` communicates to a server via a ``ClientTransport``. -/// -/// You can start RPCs to the server by calling the corresponding method: -/// - ``unary(request:descriptor:serializer:deserializer:options:handler:)`` -/// - ``clientStreaming(request:descriptor:serializer:deserializer:options:handler:)`` -/// - ``serverStreaming(request:descriptor:serializer:deserializer:options:handler:)`` -/// - ``bidirectionalStreaming(request:descriptor:serializer:deserializer:options:handler:)`` -/// -/// However, in most cases you should prefer wrapping the ``GRPCClient`` with a generated stub. -/// -/// You can set ``ServiceConfig``s on this client to override whatever configurations have been -/// set on the given transport. You can also use ``ClientInterceptor``s to implement cross-cutting -/// logic which apply to all RPCs. Example uses of interceptors include authentication and logging. -/// -/// ## Creating and configuring a client -/// -/// The following example demonstrates how to create and configure a client. -/// -/// ```swift -/// // Create a configuration object for the client and override the timeout for the 'Get' method on -/// // the 'echo.Echo' service. This configuration takes precedence over any set by the transport. -/// var configuration = GRPCClient.Configuration() -/// configuration.service.override = ServiceConfig( -/// methodConfig: [ -/// MethodConfig( -/// names: [ -/// MethodConfig.Name(service: "echo.Echo", method: "Get") -/// ], -/// timeout: .seconds(5) -/// ) -/// ] -/// ) -/// -/// // Configure a fallback timeout for all RPCs (indicated by an empty service and method name) if -/// // no configuration is provided in the overrides or by the transport. -/// configuration.service.defaults = ServiceConfig( -/// methodConfig: [ -/// MethodConfig( -/// names: [ -/// MethodConfig.Name(service: "", method: "") -/// ], -/// timeout: .seconds(10) -/// ) -/// ] -/// ) -/// -/// // Finally create a transport and instantiate the client, adding an interceptor. -/// let inProcessServerTransport = InProcessServerTransport() -/// let inProcessClientTransport = InProcessClientTransport(serverTransport: inProcessServerTransport) -/// -/// let client = GRPCClient( -/// transport: inProcessClientTransport, -/// interceptors: [StatsRecordingClientInterceptor()], -/// configuration: configuration -/// ) -/// ``` -/// -/// ## Starting and stopping the client -/// -/// Once you have configured the client, call ``run()`` to start it. Calling ``run()`` instructs the -/// transport to start connecting to the server. -/// -/// ```swift -/// // Start running the client. 'run()' must be running while RPCs are execute so it's executed in -/// // a task group. -/// try await withThrowingTaskGroup(of: Void.self) { group in -/// group.addTask { -/// try await client.run() -/// } -/// -/// // Execute a request against the "echo.Echo" service. -/// try await client.unary( -/// request: ClientRequest.Single<[UInt8]>(message: [72, 101, 108, 108, 111, 33]), -/// descriptor: MethodDescriptor(service: "echo.Echo", method: "Get"), -/// serializer: IdentitySerializer(), -/// deserializer: IdentityDeserializer(), -/// ) { response in -/// print(response.message) -/// } -/// -/// // The RPC has completed, close the client. -/// client.beginGracefulShutdown() -/// } -/// ``` -/// -/// The ``run()`` method won't return until the client has finished handling all requests. You can -/// signal to the client that it should stop creating new request streams by calling ``beginGracefulShutdown()``. -/// This gives the client enough time to drain any requests already in flight. To stop the client -/// more abruptly you can cancel the task running your client. If your application requires -/// additional resources that need their lifecycles managed you should consider using [Swift Service -/// Lifecycle](https://github.com/swift-server/swift-service-lifecycle). -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -public final class GRPCClient: Sendable { - /// The transport which provides a bidirectional communication channel with the server. - private let transport: any ClientTransport - - /// A collection of interceptors providing cross-cutting functionality to each accepted RPC. - /// - /// The order in which interceptors are added reflects the order in which they are called. The - /// first interceptor added will be the first interceptor to intercept each request. The last - /// interceptor added will be the final interceptor to intercept each request before calling - /// the appropriate handler. - private let interceptors: [any ClientInterceptor] - - /// The current state of the client. - private let state: Mutex - - /// The state of the client. - private enum State: Sendable { - /// The client hasn't been started yet. Can transition to `running` or `stopped`. - case notStarted - /// The client is running and can send RPCs. Can transition to `stopping`. - case running - /// The client is stopping and no new RPCs will be sent. Existing RPCs may run to - /// completion. May transition to `stopped`. - case stopping - /// The client has stopped, no RPCs are in flight and no more will be accepted. This state - /// is terminal. - case stopped - - mutating func run() throws { - switch self { - case .notStarted: - self = .running - - case .running: - throw RuntimeError( - code: .clientIsAlreadyRunning, - message: "The client is already running and can only be started once." - ) - - case .stopping, .stopped: - throw RuntimeError( - code: .clientIsStopped, - message: "The client has stopped and can only be started once." - ) - } - } - - mutating func stopped() { - self = .stopped - } - - mutating func beginGracefulShutdown() -> Bool { - switch self { - case .notStarted: - self = .stopped - return false - case .running: - self = .stopping - return true - case .stopping, .stopped: - return false - } - } - - func checkExecutable() throws { - switch self { - case .notStarted, .running: - // Allow .notStarted as making a request can race with 'run()'. Transports should tolerate - // queuing the request if not yet started. - () - case .stopping, .stopped: - throw RuntimeError( - code: .clientIsStopped, - message: "Client has been stopped. Can't make any more RPCs." - ) - } - } - } - - /// Creates a new client with the given transport, interceptors and configuration. - /// - /// - Parameters: - /// - transport: The transport used to establish a communication channel with a server. - /// - interceptors: A collection of interceptors providing cross-cutting functionality to each - /// accepted RPC. The order in which interceptors are added reflects the order in which they - /// are called. The first interceptor added will be the first interceptor to intercept each - /// request. The last interceptor added will be the final interceptor to intercept each - /// request before calling the appropriate handler. - public init( - transport: some ClientTransport, - interceptors: [any ClientInterceptor] = [] - ) { - self.transport = transport - self.interceptors = interceptors - self.state = Mutex(.notStarted) - } - - /// Start the client. - /// - /// This returns once ``beginGracefulShutdown()`` has been called and all in-flight RPCs have finished executing. - /// If you need to abruptly stop all work you should cancel the task executing this method. - /// - /// The client, and by extension this function, can only be run once. If the client is already - /// running or has already been closed then a ``RuntimeError`` is thrown. - public func run() async throws { - try self.state.withLock { try $0.run() } - - // When this function exits the client must have stopped. - defer { - self.state.withLock { $0.stopped() } - } - - do { - try await self.transport.connect() - } catch { - throw RuntimeError( - code: .transportError, - message: "The transport threw an error while connected.", - cause: error - ) - } - } - - /// Close the client. - /// - /// The transport will be closed: this means that it will be given enough time to wait for - /// in-flight RPCs to finish executing, but no new RPCs will be accepted. You can cancel the task - /// executing ``run()`` if you want to abruptly stop in-flight RPCs. - public func beginGracefulShutdown() { - let wasRunning = self.state.withLock { $0.beginGracefulShutdown() } - if wasRunning { - self.transport.beginGracefulShutdown() - } - } - - /// Executes a unary RPC. - /// - /// - Parameters: - /// - request: The unary request. - /// - descriptor: The method descriptor for which to execute this request. - /// - serializer: A request serializer. - /// - deserializer: A response deserializer. - /// - options: Call specific options. - /// - handler: A unary response handler. - /// - /// - Returns: The return value from the `handler`. - public func unary( - request: ClientRequest.Single, - descriptor: MethodDescriptor, - serializer: some MessageSerializer, - deserializer: some MessageDeserializer, - options: CallOptions, - handler: @Sendable @escaping (ClientResponse.Single) async throws -> ReturnValue - ) async throws -> ReturnValue { - try await self.bidirectionalStreaming( - request: ClientRequest.Stream(single: request), - descriptor: descriptor, - serializer: serializer, - deserializer: deserializer, - options: options - ) { stream in - let singleResponse = await ClientResponse.Single(stream: stream) - return try await handler(singleResponse) - } - } - - /// Start a client-streaming RPC. - /// - /// - Parameters: - /// - request: The request stream. - /// - descriptor: The method descriptor for which to execute this request. - /// - serializer: A request serializer. - /// - deserializer: A response deserializer. - /// - options: Call specific options. - /// - handler: A unary response handler. - /// - /// - Returns: The return value from the `handler`. - public func clientStreaming( - request: ClientRequest.Stream, - descriptor: MethodDescriptor, - serializer: some MessageSerializer, - deserializer: some MessageDeserializer, - options: CallOptions, - handler: @Sendable @escaping (ClientResponse.Single) async throws -> ReturnValue - ) async throws -> ReturnValue { - try await self.bidirectionalStreaming( - request: request, - descriptor: descriptor, - serializer: serializer, - deserializer: deserializer, - options: options - ) { stream in - let singleResponse = await ClientResponse.Single(stream: stream) - return try await handler(singleResponse) - } - } - - /// Start a server-streaming RPC. - /// - /// - Parameters: - /// - request: The unary request. - /// - descriptor: The method descriptor for which to execute this request. - /// - serializer: A request serializer. - /// - deserializer: A response deserializer. - /// - options: Call specific options. - /// - handler: A response stream handler. - /// - /// - Returns: The return value from the `handler`. - public func serverStreaming( - request: ClientRequest.Single, - descriptor: MethodDescriptor, - serializer: some MessageSerializer, - deserializer: some MessageDeserializer, - options: CallOptions, - handler: @Sendable @escaping (ClientResponse.Stream) async throws -> ReturnValue - ) async throws -> ReturnValue { - try await self.bidirectionalStreaming( - request: ClientRequest.Stream(single: request), - descriptor: descriptor, - serializer: serializer, - deserializer: deserializer, - options: options, - handler: handler - ) - } - - /// Start a bidirectional streaming RPC. - /// - /// - Note: ``run()`` must have been called and still executing, and ``beginGracefulShutdown()`` mustn't - /// have been called. - /// - /// - Parameters: - /// - request: The streaming request. - /// - descriptor: The method descriptor for which to execute this request. - /// - serializer: A request serializer. - /// - deserializer: A response deserializer. - /// - options: Call specific options. - /// - handler: A response stream handler. - /// - /// - Returns: The return value from the `handler`. - public func bidirectionalStreaming( - request: ClientRequest.Stream, - descriptor: MethodDescriptor, - serializer: some MessageSerializer, - deserializer: some MessageDeserializer, - options: CallOptions, - handler: @Sendable @escaping (ClientResponse.Stream) async throws -> ReturnValue - ) async throws -> ReturnValue { - try self.state.withLock { try $0.checkExecutable() } - let methodConfig = self.transport.config(forMethod: descriptor) - var options = options - options.formUnion(with: methodConfig) - - return try await ClientRPCExecutor.execute( - request: request, - method: descriptor, - options: options, - serializer: serializer, - deserializer: deserializer, - transport: self.transport, - interceptors: self.interceptors, - handler: handler - ) - } -} diff --git a/Sources/GRPCCore/GRPCServer.swift b/Sources/GRPCCore/GRPCServer.swift deleted file mode 100644 index b3d99b7de..000000000 --- a/Sources/GRPCCore/GRPCServer.swift +++ /dev/null @@ -1,233 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -private import Synchronization - -/// A gRPC server. -/// -/// The server accepts connections from clients and listens on each connection for new streams -/// which are initiated by the client. Each stream maps to a single RPC. The server routes accepted -/// streams to a service to handle the RPC or rejects them with an appropriate error if no service -/// can handle the RPC. -/// -/// A ``GRPCServer`` listens with a specific transport implementation (for example, HTTP/2 or in-process), -/// and routes requests from the transport to the service instance. You can also use "interceptors", -/// to implement cross-cutting logic which apply to all accepted RPCs. Example uses of interceptors -/// include request filtering, authentication, and logging. Once requests have been intercepted -/// they are passed to a handler which in turn returns a response to send back to the client. -/// -/// ## Creating and configuring a server -/// -/// The following example demonstrates how to create and configure a server. -/// -/// ```swift -/// // Create and an in-process transport. -/// let inProcessTransport = InProcessServerTransport() -/// -/// // Create the 'Greeter' and 'Echo' services. -/// let greeter = GreeterService() -/// let echo = EchoService() -/// -/// // Create an interceptor. -/// let statsRecorder = StatsRecordingServerInterceptors() -/// -/// // Finally create the server. -/// let server = GRPCServer( -/// transport: inProcessTransport, -/// services: [greeter, echo], -/// interceptors: [statsRecorder] -/// ) -/// ``` -/// -/// ## Starting and stopping the server -/// -/// Once you have configured the server call ``serve()`` to start it. Calling ``serve()`` starts the server's -/// transport too. A ``RuntimeError`` is thrown if the transport can't be started or encounters some other -/// runtime error. -/// -/// ```swift -/// // Start running the server. -/// try await server.serve() -/// ``` -/// -/// The ``serve()`` method won't return until the server has finished handling all requests. You can -/// signal to the server that it should stop accepting new requests by calling ``beginGracefulShutdown()``. -/// This allows the server to drain existing requests gracefully. To stop the server more abruptly -/// you can cancel the task running your server. If your application requires additional resources -/// that need their lifecycles managed you should consider using [Swift Service -/// Lifecycle](https://github.com/swift-server/swift-service-lifecycle). -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -public final class GRPCServer: Sendable { - typealias Stream = RPCStream - - /// The ``ServerTransport`` implementation that the server uses to listen for new requests. - public let transport: any ServerTransport - - /// The services registered which the server is serving. - private let router: RPCRouter - - /// A collection of ``ServerInterceptor`` implementations which are applied to all accepted - /// RPCs. - /// - /// RPCs are intercepted in the order that interceptors are added. That is, a request received - /// from the client will first be intercepted by the first added interceptor followed by the - /// second, and so on. - private let interceptors: [any ServerInterceptor] - - /// The state of the server. - private let state: Mutex - - private enum State: Sendable { - /// The server hasn't been started yet. Can transition to `running` or `stopped`. - case notStarted - /// The server is running and accepting RPCs. Can transition to `stopping`. - case running - /// The server is stopping and no new RPCs will be accepted. Existing RPCs may run to - /// completion. May transition to `stopped`. - case stopping - /// The server has stopped, no RPCs are in flight and no more will be accepted. This state - /// is terminal. - case stopped - - mutating func startServing() throws { - switch self { - case .notStarted: - self = .running - - case .running: - throw RuntimeError( - code: .serverIsAlreadyRunning, - message: "The server is already running and can only be started once." - ) - - case .stopping, .stopped: - throw RuntimeError( - code: .serverIsStopped, - message: "The server has stopped and can only be started once." - ) - } - } - - mutating func beginGracefulShutdown() -> Bool { - switch self { - case .notStarted: - self = .stopped - return false - case .running: - self = .stopping - return true - case .stopping, .stopped: - // Already stopping/stopped, ignore. - return false - } - } - - mutating func stopped() { - self = .stopped - } - } - - /// Creates a new server with no resources. - /// - /// - Parameters: - /// - transport: The transport the server should listen on. - /// - services: Services offered by the server. - /// - interceptors: A collection of interceptors providing cross-cutting functionality to each - /// accepted RPC. The order in which interceptors are added reflects the order in which they - /// are called. The first interceptor added will be the first interceptor to intercept each - /// request. The last interceptor added will be the final interceptor to intercept each - /// request before calling the appropriate handler. - public convenience init( - transport: any ServerTransport, - services: [any RegistrableRPCService], - interceptors: [any ServerInterceptor] = [] - ) { - var router = RPCRouter() - for service in services { - service.registerMethods(with: &router) - } - - self.init(transport: transport, router: router, interceptors: interceptors) - } - - /// Creates a new server with no resources. - /// - /// - Parameters: - /// - transport: The transport the server should listen on. - /// - router: A ``RPCRouter`` used by the server to route accepted streams to method handlers. - /// - interceptors: A collection of interceptors providing cross-cutting functionality to each - /// accepted RPC. The order in which interceptors are added reflects the order in which they - /// are called. The first interceptor added will be the first interceptor to intercept each - /// request. The last interceptor added will be the final interceptor to intercept each - /// request before calling the appropriate handler. - public init( - transport: any ServerTransport, - router: RPCRouter, - interceptors: [any ServerInterceptor] = [] - ) { - self.state = Mutex(.notStarted) - self.transport = transport - self.router = router - self.interceptors = interceptors - } - - /// Starts the server and runs until the registered transport has closed. - /// - /// No RPCs are processed until the configured transport is listening. If the transport fails to start - /// listening, or if it encounters a runtime error, then ``RuntimeError`` is thrown. - /// - /// This function returns when the configured transport has stopped listening and all requests have been - /// handled. You can signal to the transport that it should stop listening by calling - /// ``beginGracefulShutdown()``. The server will continue to process existing requests. - /// - /// To stop the server more abruptly you can cancel the task that this function is running in. - /// - /// - Note: You can only call this function once, repeated calls will result in a - /// ``RuntimeError`` being thrown. - public func serve() async throws { - try self.state.withLock { try $0.startServing() } - - // When we exit this function the server must have stopped. - defer { - self.state.withLock { $0.stopped() } - } - - do { - try await transport.listen { stream, context in - await self.router.handle(stream: stream, context: context, interceptors: self.interceptors) - } - } catch { - throw RuntimeError( - code: .transportError, - message: "Server transport threw an error.", - cause: error - ) - } - } - - /// Signal to the server that it should stop listening for new requests. - /// - /// By calling this function you indicate to clients that they mustn't start new requests - /// against this server. Once the server has processed all requests the ``serve()`` method returns. - /// - /// Calling this on a server which is already stopping or has stopped has no effect. - public func beginGracefulShutdown() { - let wasRunning = self.state.withLock { $0.beginGracefulShutdown() } - if wasRunning { - self.transport.beginGracefulShutdown() - } - } -} diff --git a/Sources/GRPCCore/Internal/Base64.swift b/Sources/GRPCCore/Internal/Base64.swift deleted file mode 100644 index f2078331f..000000000 --- a/Sources/GRPCCore/Internal/Base64.swift +++ /dev/null @@ -1,732 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ -// This base64 implementation is heavily inspired by: - -// https://github.com/lemire/fastbase64/blob/master/src/chromiumbase64.c -/* - Copyright (c) 2015-2016, Wojciech Muła, Alfred Klomp, Daniel Lemire - (Unless otherwise stated in the source code) - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are - met: - - 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS - IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -// https://github.com/client9/stringencoders/blob/master/src/modp_b64.c -/* - The MIT License (MIT) - - Copyright (c) 2016 Nick Galbreath - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - */ - -// swift-format-ignore: DontRepeatTypeInStaticProperties -enum Base64 { - struct DecodingOptions: OptionSet { - internal let rawValue: UInt - internal init(rawValue: UInt) { self.rawValue = rawValue } - - internal static let base64UrlAlphabet = DecodingOptions(rawValue: UInt(1 << 0)) - internal static let omitPaddingCharacter = DecodingOptions(rawValue: UInt(1 << 1)) - } - - enum DecodingError: Error, Equatable { - case invalidLength - case invalidCharacter(UInt8) - case unexpectedPaddingCharacter - case unexpectedEnd - } - - static func encode(bytes: Buffer) -> String where Buffer.Element == UInt8 { - guard !bytes.isEmpty else { - return "" - } - - // In Base64, 3 bytes become 4 output characters, and we pad to the - // nearest multiple of four. - let base64StringLength = ((bytes.count + 2) / 3) * 4 - let alphabet = Base64.encodeBase64 - - return String(customUnsafeUninitializedCapacity: base64StringLength) { backingStorage in - var input = bytes.makeIterator() - var offset = 0 - while let firstByte = input.next() { - let secondByte = input.next() - let thirdByte = input.next() - - backingStorage[offset] = Base64.encode(alphabet: alphabet, firstByte: firstByte) - backingStorage[offset + 1] = Base64.encode( - alphabet: alphabet, - firstByte: firstByte, - secondByte: secondByte - ) - backingStorage[offset + 2] = Base64.encode( - alphabet: alphabet, - secondByte: secondByte, - thirdByte: thirdByte - ) - backingStorage[offset + 3] = Base64.encode(alphabet: alphabet, thirdByte: thirdByte) - offset += 4 - } - return offset - } - } - - static func decode( - string encoded: String, - options: DecodingOptions = [] - ) throws -> [UInt8] { - let decoded = try encoded.utf8.withContiguousStorageIfAvailable { - (characterPointer) -> [UInt8] in - guard characterPointer.count > 0 else { - return [] - } - - let outputLength = ((characterPointer.count + 3) / 4) * 3 - - return try characterPointer.withMemoryRebound(to: UInt8.self) { (input) -> [UInt8] in - try [UInt8](unsafeUninitializedCapacity: outputLength) { output, length in - try Self._decodeChromium(from: input, into: output, length: &length, options: options) - } - } - } - - if decoded != nil { - return decoded! - } - - var encoded = encoded - encoded.makeContiguousUTF8() - return try Self.decode(string: encoded, options: options) - } - - private static func _decodeChromium( - from inBuffer: UnsafeBufferPointer, - into outBuffer: UnsafeMutableBufferPointer, - length: inout Int, - options: DecodingOptions = [] - ) throws { - let remaining = inBuffer.count % 4 - switch (options.contains(.omitPaddingCharacter), remaining) { - case (false, 1...): - throw DecodingError.invalidLength - case (true, 1): - throw DecodingError.invalidLength - default: - // everythin alright so far - break - } - - let outputLength = ((inBuffer.count + 3) / 4) * 3 - let fullchunks = remaining == 0 ? inBuffer.count / 4 - 1 : inBuffer.count / 4 - guard outBuffer.count >= outputLength else { - preconditionFailure("Expected the out buffer to be at least as long as outputLength") - } - - try Self.withUnsafeDecodingTablesAsBufferPointers(options: options) { d0, d1, d2, d3 in - var outIndex = 0 - if fullchunks > 0 { - for chunk in 0 ..< fullchunks { - let inIndex = chunk * 4 - let a0 = inBuffer[inIndex] - let a1 = inBuffer[inIndex + 1] - let a2 = inBuffer[inIndex + 2] - let a3 = inBuffer[inIndex + 3] - var x: UInt32 = d0[Int(a0)] | d1[Int(a1)] | d2[Int(a2)] | d3[Int(a3)] - - if x >= Self.badCharacter { - // TODO: Inspect characters here better - throw DecodingError.invalidCharacter(inBuffer[inIndex]) - } - - withUnsafePointer(to: &x) { ptr in - ptr.withMemoryRebound(to: UInt8.self, capacity: 4) { newPtr in - outBuffer[outIndex] = newPtr[0] - outBuffer[outIndex + 1] = newPtr[1] - outBuffer[outIndex + 2] = newPtr[2] - outIndex += 3 - } - } - } - } - - // inIndex is the first index in the last chunk - let inIndex = fullchunks * 4 - let a0 = inBuffer[inIndex] - let a1 = inBuffer[inIndex + 1] - var a2: UInt8? - var a3: UInt8? - if inIndex + 2 < inBuffer.count, inBuffer[inIndex + 2] != Self.encodePaddingCharacter { - a2 = inBuffer[inIndex + 2] - } - if inIndex + 3 < inBuffer.count, inBuffer[inIndex + 3] != Self.encodePaddingCharacter { - a3 = inBuffer[inIndex + 3] - } - - var x: UInt32 = d0[Int(a0)] | d1[Int(a1)] | d2[Int(a2 ?? 65)] | d3[Int(a3 ?? 65)] - if x >= Self.badCharacter { - // TODO: Inspect characters here better - throw DecodingError.invalidCharacter(inBuffer[inIndex]) - } - - withUnsafePointer(to: &x) { ptr in - ptr.withMemoryRebound(to: UInt8.self, capacity: 4) { newPtr in - outBuffer[outIndex] = newPtr[0] - outIndex += 1 - if a2 != nil { - outBuffer[outIndex] = newPtr[1] - outIndex += 1 - } - if a3 != nil { - outBuffer[outIndex] = newPtr[2] - outIndex += 1 - } - } - } - - length = outIndex - } - } - - private static func withUnsafeDecodingTablesAsBufferPointers( - options: Base64.DecodingOptions, - _ body: ( - UnsafeBufferPointer, UnsafeBufferPointer, UnsafeBufferPointer, - UnsafeBufferPointer - ) throws -> R - ) rethrows -> R { - let decoding0 = options.contains(.base64UrlAlphabet) ? Self.decoding0url : Self.decoding0 - let decoding1 = options.contains(.base64UrlAlphabet) ? Self.decoding1url : Self.decoding1 - let decoding2 = options.contains(.base64UrlAlphabet) ? Self.decoding2url : Self.decoding2 - let decoding3 = options.contains(.base64UrlAlphabet) ? Self.decoding3url : Self.decoding3 - - assert(decoding0.count == 256) - assert(decoding1.count == 256) - assert(decoding2.count == 256) - assert(decoding3.count == 256) - - return try decoding0.withUnsafeBufferPointer { (d0) -> R in - try decoding1.withUnsafeBufferPointer { (d1) -> R in - try decoding2.withUnsafeBufferPointer { (d2) -> R in - try decoding3.withUnsafeBufferPointer { (d3) -> R in - try body(d0, d1, d2, d3) - } - } - } - } - } - - private static let encodePaddingCharacter: UInt8 = 61 - - private static let encodeBase64: [UInt8] = [ - UInt8(ascii: "A"), UInt8(ascii: "B"), UInt8(ascii: "C"), UInt8(ascii: "D"), - UInt8(ascii: "E"), UInt8(ascii: "F"), UInt8(ascii: "G"), UInt8(ascii: "H"), - UInt8(ascii: "I"), UInt8(ascii: "J"), UInt8(ascii: "K"), UInt8(ascii: "L"), - UInt8(ascii: "M"), UInt8(ascii: "N"), UInt8(ascii: "O"), UInt8(ascii: "P"), - UInt8(ascii: "Q"), UInt8(ascii: "R"), UInt8(ascii: "S"), UInt8(ascii: "T"), - UInt8(ascii: "U"), UInt8(ascii: "V"), UInt8(ascii: "W"), UInt8(ascii: "X"), - UInt8(ascii: "Y"), UInt8(ascii: "Z"), UInt8(ascii: "a"), UInt8(ascii: "b"), - UInt8(ascii: "c"), UInt8(ascii: "d"), UInt8(ascii: "e"), UInt8(ascii: "f"), - UInt8(ascii: "g"), UInt8(ascii: "h"), UInt8(ascii: "i"), UInt8(ascii: "j"), - UInt8(ascii: "k"), UInt8(ascii: "l"), UInt8(ascii: "m"), UInt8(ascii: "n"), - UInt8(ascii: "o"), UInt8(ascii: "p"), UInt8(ascii: "q"), UInt8(ascii: "r"), - UInt8(ascii: "s"), UInt8(ascii: "t"), UInt8(ascii: "u"), UInt8(ascii: "v"), - UInt8(ascii: "w"), UInt8(ascii: "x"), UInt8(ascii: "y"), UInt8(ascii: "z"), - UInt8(ascii: "0"), UInt8(ascii: "1"), UInt8(ascii: "2"), UInt8(ascii: "3"), - UInt8(ascii: "4"), UInt8(ascii: "5"), UInt8(ascii: "6"), UInt8(ascii: "7"), - UInt8(ascii: "8"), UInt8(ascii: "9"), UInt8(ascii: "+"), UInt8(ascii: "/"), - ] - - private static func encode(alphabet: [UInt8], firstByte: UInt8) -> UInt8 { - let index = firstByte >> 2 - return alphabet[Int(index)] - } - - private static func encode(alphabet: [UInt8], firstByte: UInt8, secondByte: UInt8?) -> UInt8 { - var index = (firstByte & 0b00000011) << 4 - if let secondByte = secondByte { - index += (secondByte & 0b11110000) >> 4 - } - return alphabet[Int(index)] - } - - private static func encode(alphabet: [UInt8], secondByte: UInt8?, thirdByte: UInt8?) -> UInt8 { - guard let secondByte = secondByte else { - // No second byte means we are just emitting padding. - return Base64.encodePaddingCharacter - } - var index = (secondByte & 0b00001111) << 2 - if let thirdByte = thirdByte { - index += (thirdByte & 0b11000000) >> 6 - } - return alphabet[Int(index)] - } - - private static func encode(alphabet: [UInt8], thirdByte: UInt8?) -> UInt8 { - guard let thirdByte = thirdByte else { - // No third byte means just padding. - return Base64.encodePaddingCharacter - } - let index = thirdByte & 0b00111111 - return alphabet[Int(index)] - } - - private static let badCharacter: UInt32 = 0x01FF_FFFF - - private static let decoding0: [UInt32] = [ - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x0000_00F8, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x0000_00FC, - 0x0000_00D0, 0x0000_00D4, 0x0000_00D8, 0x0000_00DC, 0x0000_00E0, 0x0000_00E4, - 0x0000_00E8, 0x0000_00EC, 0x0000_00F0, 0x0000_00F4, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x0000_0000, - 0x0000_0004, 0x0000_0008, 0x0000_000C, 0x0000_0010, 0x0000_0014, 0x0000_0018, - 0x0000_001C, 0x0000_0020, 0x0000_0024, 0x0000_0028, 0x0000_002C, 0x0000_0030, - 0x0000_0034, 0x0000_0038, 0x0000_003C, 0x0000_0040, 0x0000_0044, 0x0000_0048, - 0x0000_004C, 0x0000_0050, 0x0000_0054, 0x0000_0058, 0x0000_005C, 0x0000_0060, - 0x0000_0064, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x0000_0068, 0x0000_006C, 0x0000_0070, 0x0000_0074, 0x0000_0078, - 0x0000_007C, 0x0000_0080, 0x0000_0084, 0x0000_0088, 0x0000_008C, 0x0000_0090, - 0x0000_0094, 0x0000_0098, 0x0000_009C, 0x0000_00A0, 0x0000_00A4, 0x0000_00A8, - 0x0000_00AC, 0x0000_00B0, 0x0000_00B4, 0x0000_00B8, 0x0000_00BC, 0x0000_00C0, - 0x0000_00C4, 0x0000_00C8, 0x0000_00CC, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - ] - - private static let decoding1: [UInt32] = [ - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x0000_E003, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x0000_F003, - 0x0000_4003, 0x0000_5003, 0x0000_6003, 0x0000_7003, 0x0000_8003, 0x0000_9003, - 0x0000_A003, 0x0000_B003, 0x0000_C003, 0x0000_D003, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x0000_0000, - 0x0000_1000, 0x0000_2000, 0x0000_3000, 0x0000_4000, 0x0000_5000, 0x0000_6000, - 0x0000_7000, 0x0000_8000, 0x0000_9000, 0x0000_A000, 0x0000_B000, 0x0000_C000, - 0x0000_D000, 0x0000_E000, 0x0000_F000, 0x0000_0001, 0x0000_1001, 0x0000_2001, - 0x0000_3001, 0x0000_4001, 0x0000_5001, 0x0000_6001, 0x0000_7001, 0x0000_8001, - 0x0000_9001, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x0000_A001, 0x0000_B001, 0x0000_C001, 0x0000_D001, 0x0000_E001, - 0x0000_F001, 0x0000_0002, 0x0000_1002, 0x0000_2002, 0x0000_3002, 0x0000_4002, - 0x0000_5002, 0x0000_6002, 0x0000_7002, 0x0000_8002, 0x0000_9002, 0x0000_A002, - 0x0000_B002, 0x0000_C002, 0x0000_D002, 0x0000_E002, 0x0000_F002, 0x0000_0003, - 0x0000_1003, 0x0000_2003, 0x0000_3003, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - ] - - private static let decoding2: [UInt32] = [ - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x0080_0F00, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x00C0_0F00, - 0x0000_0D00, 0x0040_0D00, 0x0080_0D00, 0x00C0_0D00, 0x0000_0E00, 0x0040_0E00, - 0x0080_0E00, 0x00C0_0E00, 0x0000_0F00, 0x0040_0F00, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x0000_0000, - 0x0040_0000, 0x0080_0000, 0x00C0_0000, 0x0000_0100, 0x0040_0100, 0x0080_0100, - 0x00C0_0100, 0x0000_0200, 0x0040_0200, 0x0080_0200, 0x00C0_0200, 0x0000_0300, - 0x0040_0300, 0x0080_0300, 0x00C0_0300, 0x0000_0400, 0x0040_0400, 0x0080_0400, - 0x00C0_0400, 0x0000_0500, 0x0040_0500, 0x0080_0500, 0x00C0_0500, 0x0000_0600, - 0x0040_0600, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x0080_0600, 0x00C0_0600, 0x0000_0700, 0x0040_0700, 0x0080_0700, - 0x00C0_0700, 0x0000_0800, 0x0040_0800, 0x0080_0800, 0x00C0_0800, 0x0000_0900, - 0x0040_0900, 0x0080_0900, 0x00C0_0900, 0x0000_0A00, 0x0040_0A00, 0x0080_0A00, - 0x00C0_0A00, 0x0000_0B00, 0x0040_0B00, 0x0080_0B00, 0x00C0_0B00, 0x0000_0C00, - 0x0040_0C00, 0x0080_0C00, 0x00C0_0C00, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - ] - - private static let decoding3: [UInt32] = [ - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x003E_0000, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x003F_0000, - 0x0034_0000, 0x0035_0000, 0x0036_0000, 0x0037_0000, 0x0038_0000, 0x0039_0000, - 0x003A_0000, 0x003B_0000, 0x003C_0000, 0x003D_0000, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x0000_0000, - 0x0001_0000, 0x0002_0000, 0x0003_0000, 0x0004_0000, 0x0005_0000, 0x0006_0000, - 0x0007_0000, 0x0008_0000, 0x0009_0000, 0x000A_0000, 0x000B_0000, 0x000C_0000, - 0x000D_0000, 0x000E_0000, 0x000F_0000, 0x0010_0000, 0x0011_0000, 0x0012_0000, - 0x0013_0000, 0x0014_0000, 0x0015_0000, 0x0016_0000, 0x0017_0000, 0x0018_0000, - 0x0019_0000, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x001A_0000, 0x001B_0000, 0x001C_0000, 0x001D_0000, 0x001E_0000, - 0x001F_0000, 0x0020_0000, 0x0021_0000, 0x0022_0000, 0x0023_0000, 0x0024_0000, - 0x0025_0000, 0x0026_0000, 0x0027_0000, 0x0028_0000, 0x0029_0000, 0x002A_0000, - 0x002B_0000, 0x002C_0000, 0x002D_0000, 0x002E_0000, 0x002F_0000, 0x0030_0000, - 0x0031_0000, 0x0032_0000, 0x0033_0000, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - ] - - private static let decoding0url: [UInt32] = [ - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, // 0 - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, // 6 - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, // 12 - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, // 18 - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, // 24 - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, // 30 - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, // 36 - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x0000_00F8, 0x01FF_FFFF, 0x01FF_FFFF, // 42 - 0x0000_00D0, 0x0000_00D4, 0x0000_00D8, 0x0000_00DC, 0x0000_00E0, 0x0000_00E4, // 48 - 0x0000_00E8, 0x0000_00EC, 0x0000_00F0, 0x0000_00F4, 0x01FF_FFFF, 0x01FF_FFFF, // 54 - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x0000_0000, // 60 - 0x0000_0004, 0x0000_0008, 0x0000_000C, 0x0000_0010, 0x0000_0014, 0x0000_0018, // 66 - 0x0000_001C, 0x0000_0020, 0x0000_0024, 0x0000_0028, 0x0000_002C, 0x0000_0030, // 72 - 0x0000_0034, 0x0000_0038, 0x0000_003C, 0x0000_0040, 0x0000_0044, 0x0000_0048, // 78 - 0x0000_004C, 0x0000_0050, 0x0000_0054, 0x0000_0058, 0x0000_005C, 0x0000_0060, // 84 - 0x0000_0064, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x0000_00FC, // 90 - 0x01FF_FFFF, 0x0000_0068, 0x0000_006C, 0x0000_0070, 0x0000_0074, 0x0000_0078, - 0x0000_007C, 0x0000_0080, 0x0000_0084, 0x0000_0088, 0x0000_008C, 0x0000_0090, - 0x0000_0094, 0x0000_0098, 0x0000_009C, 0x0000_00A0, 0x0000_00A4, 0x0000_00A8, - 0x0000_00AC, 0x0000_00B0, 0x0000_00B4, 0x0000_00B8, 0x0000_00BC, 0x0000_00C0, - 0x0000_00C4, 0x0000_00C8, 0x0000_00CC, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - ] - - private static let decoding1url: [UInt32] = [ - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, // 0 - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, // 6 - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, // 12 - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, // 18 - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, // 24 - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, // 30 - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, // 36 - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x0000_E003, 0x01FF_FFFF, 0x01FF_FFFF, // 42 - 0x0000_4003, 0x0000_5003, 0x0000_6003, 0x0000_7003, 0x0000_8003, 0x0000_9003, // 48 - 0x0000_A003, 0x0000_B003, 0x0000_C003, 0x0000_D003, 0x01FF_FFFF, 0x01FF_FFFF, // 54 - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x0000_0000, // 60 - 0x0000_1000, 0x0000_2000, 0x0000_3000, 0x0000_4000, 0x0000_5000, 0x0000_6000, // 66 - 0x0000_7000, 0x0000_8000, 0x0000_9000, 0x0000_A000, 0x0000_B000, 0x0000_C000, // 72 - 0x0000_D000, 0x0000_E000, 0x0000_F000, 0x0000_0001, 0x0000_1001, 0x0000_2001, // 78 - 0x0000_3001, 0x0000_4001, 0x0000_5001, 0x0000_6001, 0x0000_7001, 0x0000_8001, // 84 - 0x0000_9001, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x0000_F003, // 90 - 0x01FF_FFFF, 0x0000_A001, 0x0000_B001, 0x0000_C001, 0x0000_D001, 0x0000_E001, - 0x0000_F001, 0x0000_0002, 0x0000_1002, 0x0000_2002, 0x0000_3002, 0x0000_4002, - 0x0000_5002, 0x0000_6002, 0x0000_7002, 0x0000_8002, 0x0000_9002, 0x0000_A002, - 0x0000_B002, 0x0000_C002, 0x0000_D002, 0x0000_E002, 0x0000_F002, 0x0000_0003, - 0x0000_1003, 0x0000_2003, 0x0000_3003, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - ] - - private static let decoding2url: [UInt32] = [ - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, // 0 - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, // 6 - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, // 12 - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, // 18 - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, // 24 - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, // 30 - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, // 36 - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x0080_0F00, 0x01FF_FFFF, 0x01FF_FFFF, // 42 - 0x0000_0D00, 0x0040_0D00, 0x0080_0D00, 0x00C0_0D00, 0x0000_0E00, 0x0040_0E00, // 48 - 0x0080_0E00, 0x00C0_0E00, 0x0000_0F00, 0x0040_0F00, 0x01FF_FFFF, 0x01FF_FFFF, // 54 - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x0000_0000, // 60 - 0x0040_0000, 0x0080_0000, 0x00C0_0000, 0x0000_0100, 0x0040_0100, 0x0080_0100, // 66 - 0x00C0_0100, 0x0000_0200, 0x0040_0200, 0x0080_0200, 0x00C0_0200, 0x0000_0300, // 72 - 0x0040_0300, 0x0080_0300, 0x00C0_0300, 0x0000_0400, 0x0040_0400, 0x0080_0400, // 78 - 0x00C0_0400, 0x0000_0500, 0x0040_0500, 0x0080_0500, 0x00C0_0500, 0x0000_0600, // 84 - 0x0040_0600, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x00C0_0F00, // 90 - 0x01FF_FFFF, 0x0080_0600, 0x00C0_0600, 0x0000_0700, 0x0040_0700, 0x0080_0700, - 0x00C0_0700, 0x0000_0800, 0x0040_0800, 0x0080_0800, 0x00C0_0800, 0x0000_0900, - 0x0040_0900, 0x0080_0900, 0x00C0_0900, 0x0000_0A00, 0x0040_0A00, 0x0080_0A00, - 0x00C0_0A00, 0x0000_0B00, 0x0040_0B00, 0x0080_0B00, 0x00C0_0B00, 0x0000_0C00, - 0x0040_0C00, 0x0080_0C00, 0x00C0_0C00, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - ] - - private static let decoding3url: [UInt32] = [ - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, // 0 - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, // 6 - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, // 12 - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, // 18 - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, // 24 - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, // 30 - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, // 36 - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x003E_0000, 0x01FF_FFFF, 0x01FF_FFFF, // 42 - 0x0034_0000, 0x0035_0000, 0x0036_0000, 0x0037_0000, 0x0038_0000, 0x0039_0000, // 48 - 0x003A_0000, 0x003B_0000, 0x003C_0000, 0x003D_0000, 0x01FF_FFFF, 0x01FF_FFFF, // 54 - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x0000_0000, // 60 - 0x0001_0000, 0x0002_0000, 0x0003_0000, 0x0004_0000, 0x0005_0000, 0x0006_0000, // 66 - 0x0007_0000, 0x0008_0000, 0x0009_0000, 0x000A_0000, 0x000B_0000, 0x000C_0000, // 72 - 0x000D_0000, 0x000E_0000, 0x000F_0000, 0x0010_0000, 0x0011_0000, 0x0012_0000, // 78 - 0x0013_0000, 0x0014_0000, 0x0015_0000, 0x0016_0000, 0x0017_0000, 0x0018_0000, // 84 - 0x0019_0000, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x003F_0000, // 90 - 0x01FF_FFFF, 0x001A_0000, 0x001B_0000, 0x001C_0000, 0x001D_0000, 0x001E_0000, - 0x001F_0000, 0x0020_0000, 0x0021_0000, 0x0022_0000, 0x0023_0000, 0x0024_0000, - 0x0025_0000, 0x0026_0000, 0x0027_0000, 0x0028_0000, 0x0029_0000, 0x002A_0000, - 0x002B_0000, 0x002C_0000, 0x002D_0000, 0x002E_0000, 0x002F_0000, 0x0030_0000, - 0x0031_0000, 0x0032_0000, 0x0033_0000, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, - ] -} - -extension String { - /// This is a backport of a proposed String initializer that will allow writing directly into an uninitialized String's backing memory. - /// - /// As this API does not exist prior to 5.3 on Linux, or on older Apple platforms, we fake it out with a pointer and accept the extra copy. - init( - backportUnsafeUninitializedCapacity capacity: Int, - initializingUTF8With initializer: (_ buffer: UnsafeMutableBufferPointer) throws -> Int - ) rethrows { - - // The buffer will store zero terminated C string - let buffer = UnsafeMutableBufferPointer.allocate(capacity: capacity + 1) - defer { - buffer.deallocate() - } - - let initializedCount = try initializer(buffer) - precondition(initializedCount <= capacity, "Overran buffer in initializer!") - - // add zero termination - buffer[initializedCount] = 0 - - self = String(cString: buffer.baseAddress!) - } - - init( - customUnsafeUninitializedCapacity capacity: Int, - initializingUTF8With initializer: (_ buffer: UnsafeMutableBufferPointer) throws -> Int - ) rethrows { - if #available(macOS 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, *) { - try self.init(unsafeUninitializedCapacity: capacity, initializingUTF8With: initializer) - } else { - try self.init( - backportUnsafeUninitializedCapacity: capacity, - initializingUTF8With: initializer - ) - } - } -} diff --git a/Sources/GRPCCore/Internal/Concurrency Primitives/Lock.swift b/Sources/GRPCCore/Internal/Concurrency Primitives/Lock.swift deleted file mode 100644 index 2867e9982..000000000 --- a/Sources/GRPCCore/Internal/Concurrency Primitives/Lock.swift +++ /dev/null @@ -1,291 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ -//===----------------------------------------------------------------------===// -// -// This source file is part of the SwiftNIO open source project -// -// Copyright (c) 2017-2022 Apple Inc. and the SwiftNIO project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of SwiftNIO project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -#if canImport(Darwin) -public import Darwin // should be @usableFromInline -#elseif canImport(Glibc) -public import Glibc // should be @usableFromInline -#endif - -@usableFromInline -typealias LockPrimitive = pthread_mutex_t - -@usableFromInline -enum LockOperations {} - -extension LockOperations { - @inlinable - static func create(_ mutex: UnsafeMutablePointer) { - mutex.assertValidAlignment() - - var attr = pthread_mutexattr_t() - pthread_mutexattr_init(&attr) - - let err = pthread_mutex_init(mutex, &attr) - precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)") - } - - @inlinable - static func destroy(_ mutex: UnsafeMutablePointer) { - mutex.assertValidAlignment() - - let err = pthread_mutex_destroy(mutex) - precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)") - } - - @inlinable - static func lock(_ mutex: UnsafeMutablePointer) { - mutex.assertValidAlignment() - - let err = pthread_mutex_lock(mutex) - precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)") - } - - @inlinable - static func unlock(_ mutex: UnsafeMutablePointer) { - mutex.assertValidAlignment() - - let err = pthread_mutex_unlock(mutex) - precondition(err == 0, "\(#function) failed in pthread_mutex with error \(err)") - } -} - -// Tail allocate both the mutex and a generic value using ManagedBuffer. -// Both the header pointer and the elements pointer are stable for -// the class's entire lifetime. -// -// However, for safety reasons, we elect to place the lock in the "elements" -// section of the buffer instead of the head. The reasoning here is subtle, -// so buckle in. -// -// _As a practical matter_, the implementation of ManagedBuffer ensures that -// the pointer to the header is stable across the lifetime of the class, and so -// each time you call `withUnsafeMutablePointers` or `withUnsafeMutablePointerToHeader` -// the value of the header pointer will be the same. This is because ManagedBuffer uses -// `Builtin.addressOf` to load the value of the header, and that does ~magic~ to ensure -// that it does not invoke any weird Swift accessors that might copy the value. -// -// _However_, the header is also available via the `.header` field on the ManagedBuffer. -// This presents a problem! The reason there's an issue is that `Builtin.addressOf` and friends -// do not interact with Swift's exclusivity model. That is, the various `with` functions do not -// conceptually trigger a mutating access to `.header`. For elements this isn't a concern because -// there's literally no other way to perform the access, but for `.header` it's entirely possible -// to accidentally recursively read it. -// -// Our implementation is free from these issues, so we don't _really_ need to worry about it. -// However, out of an abundance of caution, we store the Value in the header, and the LockPrimitive -// in the trailing elements. We still don't use `.header`, but it's better to be safe than sorry, -// and future maintainers will be happier that we were cautious. -// -// See also: https://github.com/apple/swift/pull/40000 -@usableFromInline -final class LockStorage: ManagedBuffer { - - @inlinable - static func create(value: Value) -> Self { - let buffer = Self.create(minimumCapacity: 1) { _ in - return value - } - let storage = unsafeDowncast(buffer, to: Self.self) - - storage.withUnsafeMutablePointers { _, lockPtr in - LockOperations.create(lockPtr) - } - - return storage - } - - @inlinable - func lock() { - self.withUnsafeMutablePointerToElements { lockPtr in - LockOperations.lock(lockPtr) - } - } - - @inlinable - func unlock() { - self.withUnsafeMutablePointerToElements { lockPtr in - LockOperations.unlock(lockPtr) - } - } - - @inlinable - deinit { - self.withUnsafeMutablePointerToElements { lockPtr in - LockOperations.destroy(lockPtr) - } - } - - @inlinable - func withLockPrimitive( - _ body: (UnsafeMutablePointer) throws -> T - ) rethrows -> T { - try self.withUnsafeMutablePointerToElements { lockPtr in - return try body(lockPtr) - } - } - - @inlinable - func withLockedValue(_ mutate: (inout Value) throws -> T) rethrows -> T { - try self.withUnsafeMutablePointers { valuePtr, lockPtr in - LockOperations.lock(lockPtr) - defer { LockOperations.unlock(lockPtr) } - return try mutate(&valuePtr.pointee) - } - } -} - -extension LockStorage: @unchecked Sendable {} - -/// A threading lock based on `libpthread` instead of `libdispatch`. -/// -/// - note: ``Lock`` has reference semantics. -/// -/// This object provides a lock on top of a single `pthread_mutex_t`. This kind -/// of lock is safe to use with `libpthread`-based threading models, such as the -/// one used by NIO. On Windows, the lock is based on the substantially similar -/// `SRWLOCK` type. -@usableFromInline -struct Lock { - @usableFromInline - internal let _storage: LockStorage - - /// Create a new lock. - @inlinable - init() { - self._storage = .create(value: ()) - } - - /// Acquire the lock. - /// - /// Whenever possible, consider using `withLock` instead of this method and - /// `unlock`, to simplify lock handling. - @inlinable - func lock() { - self._storage.lock() - } - - /// Release the lock. - /// - /// Whenever possible, consider using `withLock` instead of this method and - /// `lock`, to simplify lock handling. - @inlinable - func unlock() { - self._storage.unlock() - } - - @inlinable - internal func withLockPrimitive( - _ body: (UnsafeMutablePointer) throws -> T - ) rethrows -> T { - return try self._storage.withLockPrimitive(body) - } -} - -extension Lock { - /// Acquire the lock for the duration of the given block. - /// - /// This convenience method should be preferred to `lock` and `unlock` in - /// most situations, as it ensures that the lock will be released regardless - /// of how `body` exits. - /// - /// - Parameter body: The block to execute while holding the lock. - /// - Returns: The value returned by the block. - @inlinable - func withLock(_ body: () throws -> T) rethrows -> T { - self.lock() - defer { - self.unlock() - } - return try body() - } -} - -extension Lock: Sendable {} - -extension UnsafeMutablePointer { - @inlinable - func assertValidAlignment() { - assert(UInt(bitPattern: self) % UInt(MemoryLayout.alignment) == 0) - } -} - -@usableFromInline -struct LockedValueBox { - @usableFromInline - let storage: LockStorage - - @inlinable - init(_ value: Value) { - self.storage = .create(value: value) - } - - @inlinable - func withLockedValue(_ mutate: (inout Value) throws -> T) rethrows -> T { - return try self.storage.withLockedValue(mutate) - } - - /// An unsafe view over the locked value box. - /// - /// Prefer ``withLockedValue(_:)`` where possible. - @usableFromInline - var unsafe: Unsafe { - Unsafe(storage: self.storage) - } - - @usableFromInline - struct Unsafe { - @usableFromInline - let storage: LockStorage - - /// Manually acquire the lock. - @inlinable - func lock() { - self.storage.lock() - } - - /// Manually release the lock. - @inlinable - func unlock() { - self.storage.unlock() - } - - /// Mutate the value, assuming the lock has been acquired manually. - @inlinable - func withValueAssumingLockIsAcquired( - _ mutate: (inout Value) throws -> T - ) rethrows -> T { - return try self.storage.withUnsafeMutablePointerToHeader { value in - try mutate(&value.pointee) - } - } - } -} - -extension LockedValueBox: Sendable where Value: Sendable {} diff --git a/Sources/GRPCCore/Internal/Concurrency Primitives/UnsafeTransfer.swift b/Sources/GRPCCore/Internal/Concurrency Primitives/UnsafeTransfer.swift deleted file mode 100644 index bc82d0255..000000000 --- a/Sources/GRPCCore/Internal/Concurrency Primitives/UnsafeTransfer.swift +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -@usableFromInline -struct UnsafeTransfer { - @usableFromInline - var wrappedValue: Wrapped - - @inlinable - init(_ wrappedValue: Wrapped) { - self.wrappedValue = wrappedValue - } -} - -extension UnsafeTransfer: @unchecked Sendable {} diff --git a/Sources/GRPCCore/Internal/Metadata+GRPC.swift b/Sources/GRPCCore/Internal/Metadata+GRPC.swift deleted file mode 100644 index 9bff423e3..000000000 --- a/Sources/GRPCCore/Internal/Metadata+GRPC.swift +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -extension Metadata { - @inlinable - var previousRPCAttempts: Int? { - get { - self.firstString(forKey: .previousRPCAttempts).flatMap { Int($0) } - } - set { - if let newValue = newValue { - self.replaceOrAddString(String(describing: newValue), forKey: .previousRPCAttempts) - } else { - self.removeAllValues(forKey: .previousRPCAttempts) - } - } - } - - @inlinable - var retryPushback: RetryPushback? { - return self.firstString(forKey: .retryPushbackMs).map { - RetryPushback(milliseconds: $0) - } - } - - @inlinable - var timeout: Duration? { - get { - self.firstString(forKey: .timeout).flatMap { Timeout(decoding: $0)?.duration } - } - set { - if let newValue { - self.replaceOrAddString(String(describing: Timeout(duration: newValue)), forKey: .timeout) - } else { - self.removeAllValues(forKey: .timeout) - } - } - } -} - -extension Metadata { - @usableFromInline - enum GRPCKey: String, Sendable, Hashable { - case timeout = "grpc-timeout" - case retryPushbackMs = "grpc-retry-pushback-ms" - case previousRPCAttempts = "grpc-previous-rpc-attempts" - } - - @inlinable - func firstString(forKey key: GRPCKey) -> String? { - self[stringValues: key.rawValue].first(where: { _ in true }) - } - - @inlinable - mutating func replaceOrAddString(_ value: String, forKey key: GRPCKey) { - self.replaceOrAddString(value, forKey: key.rawValue) - } - - @inlinable - mutating func removeAllValues(forKey key: GRPCKey) { - self.removeAllValues(forKey: key.rawValue) - } -} - -extension Metadata { - @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) - @usableFromInline - enum RetryPushback: Hashable, Sendable { - case retryAfter(Duration) - case stopRetrying - - @inlinable - init(milliseconds value: String) { - if let milliseconds = Int64(value), milliseconds >= 0 { - let (seconds, remainingMilliseconds) = milliseconds.quotientAndRemainder(dividingBy: 1000) - // 1e18 attoseconds per second - // 1e15 attoseconds per millisecond. - let attoseconds = Int64(remainingMilliseconds) * 1_000_000_000_000_000 - self = .retryAfter(Duration(secondsComponent: seconds, attosecondsComponent: attoseconds)) - } else { - // Negative or not parseable means stop trying. - // Source: https://github.com/grpc/proposal/blob/master/A6-client-retries.md - self = .stopRetrying - } - } - } -} diff --git a/Sources/GRPCCore/Internal/MethodConfigs.swift b/Sources/GRPCCore/Internal/MethodConfigs.swift deleted file mode 100644 index 1992b59a8..000000000 --- a/Sources/GRPCCore/Internal/MethodConfigs.swift +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -/// A collection of ``MethodConfig``s, mapped to specific methods or services. -/// -/// When creating a new instance, no overrides and no default will be set for using when getting -/// a configuration for a method that has not been given a specific override. -/// Use ``setDefaultConfig(_:forService:)`` to set a specific override for a whole -/// service, or set a default configuration for all methods by calling ``setDefaultConfig(_:)``. -/// -/// Use the subscript to get and set configurations for specific methods. -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -package struct MethodConfigs: Sendable, Hashable { - private var elements: [MethodConfig.Name: MethodConfig] - - /// Create a new ``_MethodConfigs``. - /// - /// - Parameter serviceConfig: The configuration to read ``MethodConfig`` from. - package init(serviceConfig: ServiceConfig = ServiceConfig()) { - self.elements = [:] - - for configuration in serviceConfig.methodConfig { - for name in configuration.names { - self.elements[name] = configuration - } - } - } - - /// Get or set the corresponding ``MethodConfig`` for the given ``MethodDescriptor``. - /// - /// Configuration is hierarchical and can be set per-method, per-service - /// (``setDefaultConfig(_:forService:)``) and globally (``setDefaultConfig(_:)``). - /// This subscript sets the per-method configuration but retrieves a configuration respecting - /// the hierarchy. If no per-method configuration is present, the per-service configuration is - /// checked and returned if present. If the per-service configuration isn't present then the - /// global configuration is returned, if present. - /// - /// - Parameters: - /// - descriptor: The ``MethodDescriptor`` for which to get or set a ``MethodConfig``. - package subscript(_ descriptor: MethodDescriptor) -> MethodConfig? { - get { - var name = MethodConfig.Name(service: descriptor.service, method: descriptor.method) - - if let configuration = self.elements[name] { - return configuration - } - - // Check if the config is set at the service level by clearing the method. - name.method = "" - - if let configuration = self.elements[name] { - return configuration - } - - // Check if the config is set at the global level by clearing the service and method. - name.service = "" - return self.elements[name] - } - - set { - let name = MethodConfig.Name(service: descriptor.service, method: descriptor.method) - self.elements[name] = newValue - } - } - - /// Set a default configuration for all methods that have no overrides. - /// - /// - Parameter config: The default configuration. - package mutating func setDefaultConfig(_ config: MethodConfig?) { - let name = MethodConfig.Name(service: "", method: "") - self.elements[name] = config - } - - /// Set a default configuration for a service. - /// - /// If getting a configuration for a method that's part of a service, and the method itself doesn't have an - /// override, then this configuration will be used instead of the default configuration passed when creating - /// this instance of ``MethodConfigs``. - /// - /// - Parameters: - /// - config: The default configuration for the service. - /// - service: The name of the service for which this override applies. - package mutating func setDefaultConfig( - _ config: MethodConfig?, - forService service: String - ) { - let name = MethodConfig.Name(service: "", method: "") - self.elements[name] = config - } -} diff --git a/Sources/GRPCCore/Internal/Result+Catching.swift b/Sources/GRPCCore/Internal/Result+Catching.swift deleted file mode 100644 index 68bbbebd7..000000000 --- a/Sources/GRPCCore/Internal/Result+Catching.swift +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -extension Result where Failure == any Error { - /// Like `Result(catching:)`, but `async`. - /// - /// - Parameter body: An `async` closure to catch the result of. - @inlinable - init(catching body: () async throws -> Success) async { - do { - self = .success(try await body()) - } catch { - self = .failure(error) - } - } - - /// Attempts to map the error to the given error type. - /// - /// If the cast fails then the provided closure is used to create an error of the given type. - /// - /// - Parameters: - /// - errorType: The type of error to cast to. - /// - buildError: A closure which constructs the desired error if the cast fails. - @inlinable - func castError( - to errorType: NewError.Type = NewError.self, - or buildError: (any Error) -> NewError - ) -> Result { - return self.mapError { error in - return (error as? NewError) ?? buildError(error) - } - } -} diff --git a/Sources/GRPCCore/Internal/String+Extensions.swift b/Sources/GRPCCore/Internal/String+Extensions.swift deleted file mode 100644 index f230c1ffe..000000000 --- a/Sources/GRPCCore/Internal/String+Extensions.swift +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ -//===----------------------------------------------------------------------===// -// -// This source file is part of the SwiftNIO open source project -// -// Copyright (c) 2017-2023 Apple Inc. and the SwiftNIO project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of SwiftNIO project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// - -extension UInt8 { - @inlinable - var isASCII: Bool { - return self <= 127 - } -} - -extension String.UTF8View { - /// Compares two UTF8 strings as case insensitive ASCII bytes. - /// - /// - Parameter bytes: The string constant in the form of a collection of `UInt8` - /// - Returns: Whether the collection contains **EXACTLY** this array or no, but by ignoring case. - @inlinable - func compareCaseInsensitiveASCIIBytes(to other: String.UTF8View) -> Bool { - // fast path: we can get the underlying bytes of both - let maybeMaybeResult = self.withContiguousStorageIfAvailable { lhsBuffer -> Bool? in - other.withContiguousStorageIfAvailable { rhsBuffer in - if lhsBuffer.count != rhsBuffer.count { - return false - } - - for idx in 0 ..< lhsBuffer.count { - // let's hope this gets vectorised ;) - if lhsBuffer[idx] & 0xdf != rhsBuffer[idx] & 0xdf && lhsBuffer[idx].isASCII { - return false - } - } - return true - } - } - - if let maybeResult = maybeMaybeResult, let result = maybeResult { - return result - } else { - return self._compareCaseInsensitiveASCIIBytesSlowPath(to: other) - } - } - - @inlinable - @inline(never) - func _compareCaseInsensitiveASCIIBytesSlowPath(to other: String.UTF8View) -> Bool { - return self.elementsEqual(other, by: { return (($0 & 0xdf) == ($1 & 0xdf) && $0.isASCII) }) - } -} - -extension String { - @inlinable - func isEqualCaseInsensitiveASCIIBytes(to: String) -> Bool { - return self.utf8.compareCaseInsensitiveASCIIBytes(to: to.utf8) - } -} diff --git a/Sources/GRPCCore/Internal/TaskGroup+CancellableTask.swift b/Sources/GRPCCore/Internal/TaskGroup+CancellableTask.swift deleted file mode 100644 index 454a85b85..000000000 --- a/Sources/GRPCCore/Internal/TaskGroup+CancellableTask.swift +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) -extension TaskGroup { - /// Adds a child task to the group which is individually cancellable. - /// - /// - Parameter operation: The task to add to the group. - /// - Returns: A handle which can be used to cancel the task without cancelling the rest of - /// the group. - @inlinable - mutating func addCancellableTask( - _ operation: @Sendable @escaping () async -> ChildTaskResult - ) -> CancellableTaskHandle { - let signal = AsyncStream.makeStream(of: Void.self) - self.addTask { - return await withTaskGroup( - of: _ResultOrCancelled.self, - returning: ChildTaskResult.self - ) { group in - group.addTask { - let childTaskResult = await operation() - return .result(childTaskResult) - } - - group.addTask { - for await _ in signal.stream {} - return .cancelled - } - - let first = await group.next()! - group.cancelAll() - let second = await group.next()! - - switch (first, second) { - case (.result(let result), .cancelled), (.cancelled, .result(let result)): - return result - default: - fatalError("Internal inconsistency") - } - } - } - - return CancellableTaskHandle(continuation: signal.continuation) - } - - @usableFromInline - enum _ResultOrCancelled: Sendable { - case result(ChildTaskResult) - case cancelled - } -} - -@usableFromInline -@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) -struct CancellableTaskHandle: Sendable { - @usableFromInline - private(set) var continuation: AsyncStream.Continuation - - @inlinable - init(continuation: AsyncStream.Continuation) { - self.continuation = continuation - } - - @inlinable - func cancel() { - self.continuation.finish() - } -} diff --git a/Sources/GRPCCore/Metadata.swift b/Sources/GRPCCore/Metadata.swift deleted file mode 100644 index 8326eb336..000000000 --- a/Sources/GRPCCore/Metadata.swift +++ /dev/null @@ -1,496 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -/// A collection of metadata key-value pairs, found in RPC streams. -/// -/// Metadata is a side channel associated with an RPC, that allows you to send information between clients -/// and servers. Metadata is stored as a list of key-value pairs where keys aren't required to be unique; -/// a single key may have multiple values associated with it. -/// -/// Keys are case-insensitive ASCII strings. Values may be ASCII strings or binary data. The keys -/// for binary data should end with "-bin": this will be asserted when adding a new binary value. -/// Keys must not be prefixed with "grpc-" as these are reserved for gRPC. -/// -/// # Using Metadata -/// -/// You can add values to ``Metadata`` using the ``addString(_:forKey:)`` and -/// ``addBinary(_:forKey:)`` methods: -/// -/// ```swift -/// var metadata = Metadata() -/// metadata.addString("value", forKey: "key") -/// metadata.addBinary([118, 97, 108, 117, 101], forKey: "key-bin") -/// ``` -/// -/// As ``Metadata`` conforms to `RandomAccessCollection` you can iterate over its values. -/// Because metadata can store strings and binary values, its `Element` type is an `enum` representing -/// both possibilities: -/// -/// ```swift -/// for (key, value) in metadata { -/// switch value { -/// case .string(let value): -/// print("'\(key)' has a string value: '\(value)'") -/// case .binary(let value): -/// print("'\(key)' has a binary value: '\(value)'") -/// } -/// } -/// ``` -/// -/// You can also iterate over the values for a specific key: -/// -/// ```swift -/// for value in metadata["key"] { -/// switch value { -/// case .string(let value): -/// print("'key' has a string value: '\(value)'") -/// case .binary(let value): -/// print("'key' has a binary value: '\(value)'") -/// } -/// } -/// ``` -/// -/// You can get only string or binary values for a key using ``subscript(stringValues:)`` and -/// ``subscript(binaryValues:)``: -/// -/// ```swift -/// for value in metadata[stringValues: "key"] { -/// print("'key' has a string value: '\(value)'") -/// } -/// -/// for value in metadata[binaryValues: "key"] { -/// print("'key' has a binary value: '\(value)'") -/// } -/// ``` -/// -/// - Note: Binary values are encoded as base64 strings when they are sent over the wire, so keys with -/// the "-bin" suffix may have string values (rather than binary). These are deserialized automatically when -/// using ``subscript(binaryValues:)``. -public struct Metadata: Sendable, Hashable { - - /// A metadata value. It can either be a simple string, or binary data. - public enum Value: Sendable, Hashable { - case string(String) - case binary([UInt8]) - - /// The value as a String. If it was originally stored as a binary, the base64-encoded String version - /// of the binary data will be returned instead. - public func encoded() -> String { - switch self { - case .string(let string): - return string - case .binary(let bytes): - return Base64.encode(bytes: bytes) - } - } - } - - /// A metadata key-value pair. - internal struct KeyValuePair: Sendable, Hashable { - internal let key: String - internal let value: Value - - /// Constructor for a metadata key-value pair. - /// - /// - Parameters: - /// - key: The key for the key-value pair. - /// - value: The value to be associated to the given key. If it's a binary value, then the associated - /// key must end in "-bin", otherwise, this method will produce an assertion failure. - init(key: String, value: Value) { - if case .binary = value { - assert(key.hasSuffix("-bin"), "Keys for binary values must end in -bin") - } - self.key = key - self.value = value - } - } - - private var elements: [KeyValuePair] - - /// The Metadata collection's capacity. - public var capacity: Int { - self.elements.capacity - } - - /// Initialize an empty Metadata collection. - public init() { - self.elements = [] - } - - /// Initialize `Metadata` from a `Sequence` of `Element`s. - public init(_ elements: some Sequence) { - self.elements = elements.map { key, value in - KeyValuePair(key: key, value: value) - } - } - - /// Reserve the specified minimum capacity in the collection. - /// - /// - Parameter minimumCapacity: The minimum capacity to reserve in the collection. - public mutating func reserveCapacity(_ minimumCapacity: Int) { - self.elements.reserveCapacity(minimumCapacity) - } - - /// Add a new key-value pair, where the value is a string. - /// - /// - Parameters: - /// - stringValue: The string value to be associated with the given key. - /// - key: The key to be associated with the given value. - public mutating func addString(_ stringValue: String, forKey key: String) { - self.addValue(.string(stringValue), forKey: key) - } - - /// Add a new key-value pair, where the value is binary data, in the form of `[UInt8]`. - /// - /// - Parameters: - /// - binaryValue: The binary data (i.e., `[UInt8]`) to be associated with the given key. - /// - key: The key to be associated with the given value. Must end in "-bin". - public mutating func addBinary(_ binaryValue: [UInt8], forKey key: String) { - self.addValue(.binary(binaryValue), forKey: key) - } - - /// Add a new key-value pair. - /// - /// - Parameters: - /// - value: The ``Value`` to be associated with the given key. - /// - key: The key to be associated with the given value. If value is binary, it must end in "-bin". - internal mutating func addValue(_ value: Value, forKey key: String) { - self.elements.append(.init(key: key, value: value)) - } - - /// Removes all values associated with the given key. - /// - /// - Parameter key: The key for which all values should be removed. - /// - /// - Complexity: O(*n*), where *n* is the number of entries in the metadata instance. - public mutating func removeAllValues(forKey key: String) { - elements.removeAll { metadataKeyValue in - metadataKeyValue.key.isEqualCaseInsensitiveASCIIBytes(to: key) - } - } - - /// Adds a key-value pair to the collection, where the value is a string. - /// - /// If there are pairs already associated to the given key, they will all be removed first, and the new pair - /// will be added. If no pairs are present with the given key, a new one will be added. - /// - /// - Parameters: - /// - stringValue: The string value to be associated with the given key. - /// - key: The key to be associated with the given value. - /// - /// - Complexity: O(*n*), where *n* is the number of entries in the metadata instance. - public mutating func replaceOrAddString(_ stringValue: String, forKey key: String) { - self.replaceOrAddValue(.string(stringValue), forKey: key) - } - - /// Adds a key-value pair to the collection, where the value is `[UInt8]`. - /// - /// If there are pairs already associated to the given key, they will all be removed first, and the new pair - /// will be added. If no pairs are present with the given key, a new one will be added. - /// - /// - Parameters: - /// - binaryValue: The `[UInt8]` to be associated with the given key. - /// - key: The key to be associated with the given value. Must end in "-bin". - /// - /// - Complexity: O(*n*), where *n* is the number of entries in the metadata instance. - public mutating func replaceOrAddBinary(_ binaryValue: [UInt8], forKey key: String) { - self.replaceOrAddValue(.binary(binaryValue), forKey: key) - } - - /// Adds a key-value pair to the collection. - /// - /// If there are pairs already associated to the given key, they will all be removed first, and the new pair - /// will be added. If no pairs are present with the given key, a new one will be added. - /// - /// - Parameters: - /// - value: The ``Value`` to be associated with the given key. - /// - key: The key to be associated with the given value. If value is binary, it must end in "-bin". - /// - /// - Complexity: O(*n*), where *n* is the number of entries in the metadata instance. - internal mutating func replaceOrAddValue(_ value: Value, forKey key: String) { - self.removeAllValues(forKey: key) - self.elements.append(.init(key: key, value: value)) - } - - /// Removes all key-value pairs from this metadata instance. - /// - /// - Parameter keepingCapacity: Whether the current capacity should be kept or reset. - /// - /// - Complexity: O(*n*), where *n* is the number of entries in the metadata instance. - public mutating func removeAll(keepingCapacity: Bool) { - self.elements.removeAll(keepingCapacity: keepingCapacity) - } - - /// Removes all elements which match the given predicate. - /// - /// - Parameter predicate: Returns `true` if the element should be removed. - /// - /// - Complexity: O(*n*), where *n* is the number of entries in the metadata instance. - public mutating func removeAll( - where predicate: (_ key: String, _ value: Value) throws -> Bool - ) rethrows { - try self.elements.removeAll { pair in - try predicate(pair.key, pair.value) - } - } -} - -extension Metadata: RandomAccessCollection { - public typealias Element = (key: String, value: Value) - - public struct Index: Comparable, Sendable { - @usableFromInline - let _base: Array.Index - - @inlinable - init(_base: Array.Index) { - self._base = _base - } - - @inlinable - public static func < (lhs: Index, rhs: Index) -> Bool { - return lhs._base < rhs._base - } - } - - public var startIndex: Index { - return .init(_base: self.elements.startIndex) - } - - public var endIndex: Index { - return .init(_base: self.elements.endIndex) - } - - public func index(before i: Index) -> Index { - return .init(_base: self.elements.index(before: i._base)) - } - - public func index(after i: Index) -> Index { - return .init(_base: self.elements.index(after: i._base)) - } - - public subscript(position: Index) -> Element { - let keyValuePair = self.elements[position._base] - return (key: keyValuePair.key, value: keyValuePair.value) - } -} - -extension Metadata { - /// A sequence of metadata values for a given key. - public struct Values: Sequence, Sendable { - - /// An iterator for all metadata ``Value``s associated with a given key. - public struct Iterator: IteratorProtocol, Sendable { - private var metadataIterator: Metadata.Iterator - private let key: String - - init(forKey key: String, metadata: Metadata) { - self.metadataIterator = metadata.makeIterator() - self.key = key - } - - public mutating func next() -> Value? { - while let nextKeyValue = self.metadataIterator.next() { - if nextKeyValue.key.isEqualCaseInsensitiveASCIIBytes(to: self.key) { - return nextKeyValue.value - } - } - return nil - } - } - - private let key: String - private let metadata: Metadata - - internal init(key: String, metadata: Metadata) { - self.key = key - self.metadata = metadata - } - - public func makeIterator() -> Iterator { - Iterator(forKey: self.key, metadata: self.metadata) - } - } - - /// Get a ``Values`` sequence for a given key. - /// - /// - Parameter key: The returned sequence will only return values for this key. - /// - /// - Returns: A sequence containing all values for the given key. - public subscript(_ key: String) -> Values { - Values(key: key, metadata: self) - } -} - -extension Metadata { - - /// A sequence of metadata string values for a given key. - public struct StringValues: Sequence, Sendable { - /// An iterator for all string values associated with a given key. - /// - /// This iterator will only return values originally stored as strings for a given key. - public struct Iterator: IteratorProtocol, Sendable { - private var values: Values.Iterator - - init(values: Values) { - self.values = values.makeIterator() - } - - public mutating func next() -> String? { - while let value = self.values.next() { - switch value { - case .string(let stringValue): - return stringValue - case .binary: - continue - } - } - return nil - } - } - - private let key: String - private let metadata: Metadata - - internal init(key: String, metadata: Metadata) { - self.key = key - self.metadata = metadata - } - - public func makeIterator() -> Iterator { - Iterator(values: Values(key: self.key, metadata: self.metadata)) - } - } - - /// Get a ``StringValues`` sequence for a given key. - /// - /// - Parameter key: The returned sequence will only return string values for this key. - /// - /// - Returns: A sequence containing string values for the given key. - public subscript(stringValues key: String) -> StringValues { - StringValues(key: key, metadata: self) - } -} - -extension Metadata { - /// A sequence of metadata binary values for a given key. - public struct BinaryValues: Sequence, Sendable { - - /// An iterator for all binary data values associated with a given key. - /// - /// This iterator will return values originally stored as binary data for a given key, and will also try to - /// decode values stored as strings as if they were base64-encoded strings. - public struct Iterator: IteratorProtocol, Sendable { - private var values: Values.Iterator - - init(values: Values) { - self.values = values.makeIterator() - } - - public mutating func next() -> [UInt8]? { - while let value = self.values.next() { - switch value { - case .string(let stringValue): - do { - return try Base64.decode(string: stringValue) - } catch { - continue - } - case .binary(let binaryValue): - return binaryValue - } - } - return nil - } - } - - private let key: String - private let metadata: Metadata - - internal init(key: String, metadata: Metadata) { - self.key = key - self.metadata = metadata - } - - public func makeIterator() -> Iterator { - Iterator(values: Values(key: self.key, metadata: self.metadata)) - } - } - - /// A subscript to get a ``BinaryValues`` sequence for a given key. - /// - /// As it's iterated, this sequence will return values originally stored as binary data for a given key, and will - /// also try to decode values stored as strings as if they were base64-encoded strings; only strings that - /// are successfully decoded will be returned. - /// - /// - Parameter key: The returned sequence will only return binary (i.e. `[UInt8]`) values for this key. - /// - /// - Returns: A sequence containing binary (i.e. `[UInt8]`) values for the given key. - /// - /// - SeeAlso: ``BinaryValues/Iterator``. - public subscript(binaryValues key: String) -> BinaryValues { - BinaryValues(key: key, metadata: self) - } -} - -extension Metadata: ExpressibleByDictionaryLiteral { - public init(dictionaryLiteral elements: (String, Value)...) { - self.elements = elements.map { KeyValuePair(key: $0, value: $1) } - } -} - -extension Metadata: ExpressibleByArrayLiteral { - public init(arrayLiteral elements: (String, Value)...) { - self.elements = elements.map { KeyValuePair(key: $0, value: $1) } - } -} - -extension Metadata.Value: ExpressibleByStringLiteral { - public init(stringLiteral value: StringLiteralType) { - self = .string(value) - } -} - -extension Metadata.Value: ExpressibleByStringInterpolation { - public init(stringInterpolation: DefaultStringInterpolation) { - self = .string(String(stringInterpolation: stringInterpolation)) - } -} - -extension Metadata.Value: ExpressibleByArrayLiteral { - public typealias ArrayLiteralElement = UInt8 - - public init(arrayLiteral elements: ArrayLiteralElement...) { - self = .binary(elements) - } -} - -extension Metadata: CustomStringConvertible { - public var description: String { - String(describing: self.map({ ($0.key, $0.value) })) - } -} - -extension Metadata.Value: CustomStringConvertible { - public var description: String { - switch self { - case .string(let stringValue): - return String(describing: stringValue) - case .binary(let binaryValue): - return String(describing: binaryValue) - } - } -} diff --git a/Sources/GRPCCore/MethodDescriptor.swift b/Sources/GRPCCore/MethodDescriptor.swift deleted file mode 100644 index 8d2795ac1..000000000 --- a/Sources/GRPCCore/MethodDescriptor.swift +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -/// A description of a method on a service. -public struct MethodDescriptor: Sendable, Hashable { - /// The name of the service, including the package name. - /// - /// For example, the name of the "Greeter" service in "helloworld" package - /// is "helloworld.Greeter". - public var service: String - - /// The name of the method in the service, excluding the service name. - public var method: String - - /// The fully qualified method name in the format "package.service/method". - /// - /// For example, the fully qualified name of the "SayHello" method of the "Greeter" service in - /// "helloworld" package is "helloworld.Greeter/SayHelllo". - public var fullyQualifiedMethod: String { - "\(self.service)/\(self.method)" - } - - /// Creates a new method descriptor. - /// - /// - Parameters: - /// - service: The name of the service, including the package name. For example, - /// "helloworld.Greeter". - /// - method: The name of the method. For example, "SayHello". - public init(service: String, method: String) { - self.service = service - self.method = method - } -} diff --git a/Sources/GRPCCore/RPCError.swift b/Sources/GRPCCore/RPCError.swift deleted file mode 100644 index 7354a7b83..000000000 --- a/Sources/GRPCCore/RPCError.swift +++ /dev/null @@ -1,235 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -/// An error representing the outcome of an RPC. -/// -/// See also ``Status``. -public struct RPCError: Sendable, Hashable, Error { - /// A code representing the high-level domain of the error. - public var code: Code - - /// A message providing additional context about the error. - public var message: String - - /// Metadata associated with the error. - /// - /// Any metadata included in the error thrown from a service will be sent back to the client and - /// conversely any ``RPCError`` received by the client may include metadata sent by a service. - /// - /// Note that clients and servers may synthesise errors which may not include metadata. - public var metadata: Metadata - - /// The original error which led to this error being thrown. - public var cause: (any Error)? - - /// Create a new RPC error. - /// - /// - Parameters: - /// - code: The status code. - /// - message: A message providing additional context about the code. - /// - metadata: Any metadata to attach to the error. - /// - cause: An underlying error which led to this error being thrown. - public init(code: Code, message: String, metadata: Metadata = [:], cause: (any Error)? = nil) { - self.code = code - self.message = message - self.metadata = metadata - self.cause = cause - } - - /// Create a new RPC error from the provided ``Status``. - /// - /// Returns `nil` if the provided ``Status`` has code ``Status/Code-swift.struct/ok``. - /// - /// - Parameters: - /// - status: The status to convert. - /// - metadata: Any metadata to attach to the error. - public init?(status: Status, metadata: Metadata = [:]) { - guard let code = Code(status.code) else { return nil } - self.init(code: code, message: status.message, metadata: metadata) - } - - public func hash(into hasher: inout Hasher) { - hasher.combine(self.code) - hasher.combine(self.message) - hasher.combine(self.metadata) - } - - public static func == (lhs: RPCError, rhs: RPCError) -> Bool { - return lhs.code == rhs.code && lhs.message == rhs.message && lhs.metadata == rhs.metadata - } -} - -extension RPCError: CustomStringConvertible { - public var description: String { - if let cause = self.cause { - return "\(self.code): \"\(self.message)\" (cause: \"\(cause)\")" - } else { - return "\(self.code): \"\(self.message)\"" - } - } -} - -extension RPCError { - public struct Code: Hashable, Sendable, CustomStringConvertible { - /// The numeric value of the error code. - public var rawValue: Int { Int(self.wrapped.rawValue) } - - internal var wrapped: Status.Code.Wrapped - private init(code: Status.Code.Wrapped) { - self.wrapped = code - } - - /// Creates an error code from the given ``Status/Code-swift.struct``; returns `nil` if the - /// code is ``Status/Code-swift.struct/ok``. - /// - /// - Parameter code: The status code to create this ``RPCError/Code-swift.struct`` from. - public init?(_ code: Status.Code) { - if code == .ok { - return nil - } else { - self.wrapped = code.wrapped - } - } - - public var description: String { - String(describing: self.wrapped) - } - - package static let all: [Self] = [ - .cancelled, - .unknown, - .invalidArgument, - .deadlineExceeded, - .notFound, - .alreadyExists, - .permissionDenied, - .resourceExhausted, - .failedPrecondition, - .aborted, - .outOfRange, - .unimplemented, - .internalError, - .unavailable, - .dataLoss, - .unauthenticated, - ] - } -} - -extension RPCError.Code { - /// The operation was cancelled (typically by the caller). - public static let cancelled = Self(code: .cancelled) - - /// Unknown error. An example of where this error may be returned is if a - /// Status value received from another address space belongs to an error-space - /// that is not known in this address space. Also errors raised by APIs that - /// do not return enough error information may be converted to this error. - public static let unknown = Self(code: .unknown) - - /// Client specified an invalid argument. Note that this differs from - /// ``failedPrecondition``. ``invalidArgument`` indicates arguments that are - /// problematic regardless of the state of the system (e.g., a malformed file - /// name). - public static let invalidArgument = Self(code: .invalidArgument) - - /// Deadline expired before operation could complete. For operations that - /// change the state of the system, this error may be returned even if the - /// operation has completed successfully. For example, a successful response - /// from a server could have been delayed long enough for the deadline to - /// expire. - public static let deadlineExceeded = Self(code: .deadlineExceeded) - - /// Some requested entity (e.g., file or directory) was not found. - public static let notFound = Self(code: .notFound) - - /// Some entity that we attempted to create (e.g., file or directory) already - /// exists. - public static let alreadyExists = Self(code: .alreadyExists) - - /// The caller does not have permission to execute the specified operation. - /// ``permissionDenied`` must not be used for rejections caused by exhausting - /// some resource (use ``resourceExhausted`` instead for those errors). - /// ``permissionDenied`` must not be used if the caller can not be identified - /// (use ``unauthenticated`` instead for those errors). - public static let permissionDenied = Self(code: .permissionDenied) - - /// Some resource has been exhausted, perhaps a per-user quota, or perhaps the - /// entire file system is out of space. - public static let resourceExhausted = Self(code: .resourceExhausted) - - /// Operation was rejected because the system is not in a state required for - /// the operation's execution. For example, directory to be deleted may be - /// non-empty, an rmdir operation is applied to a non-directory, etc. - /// - /// A litmus test that may help a service implementor in deciding - /// between ``failedPrecondition``, ``aborted``, and ``unavailable``: - /// - Use ``unavailable`` if the client can retry just the failing call. - /// - Use ``aborted`` if the client should retry at a higher-level - /// (e.g., restarting a read-modify-write sequence). - /// - Use ``failedPrecondition`` if the client should not retry until - /// the system state has been explicitly fixed. E.g., if an "rmdir" - /// fails because the directory is non-empty, ``failedPrecondition`` - /// should be returned since the client should not retry unless - /// they have first fixed up the directory by deleting files from it. - /// - Use ``failedPrecondition`` if the client performs conditional - /// REST Get/Update/Delete on a resource and the resource on the - /// server does not match the condition. E.g., conflicting - /// read-modify-write on the same resource. - public static let failedPrecondition = Self(code: .failedPrecondition) - - /// The operation was aborted, typically due to a concurrency issue like - /// sequencer check failures, transaction aborts, etc. - /// - /// See litmus test above for deciding between ``failedPrecondition``, ``aborted``, - /// and ``unavailable``. - public static let aborted = Self(code: .aborted) - - /// Operation was attempted past the valid range. E.g., seeking or reading - /// past end of file. - /// - /// Unlike ``invalidArgument``, this error indicates a problem that may be fixed - /// if the system state changes. For example, a 32-bit file system will - /// generate ``invalidArgument`` if asked to read at an offset that is not in the - /// range [0,2^32-1], but it will generate ``outOfRange`` if asked to read from - /// an offset past the current file size. - /// - /// There is a fair bit of overlap between ``failedPrecondition`` and - /// ``outOfRange``. We recommend using ``outOfRange`` (the more specific error) - /// when it applies so that callers who are iterating through a space can - /// easily look for an ``outOfRange`` error to detect when they are done. - public static let outOfRange = Self(code: .outOfRange) - - /// Operation is not implemented or not supported/enabled in this service. - public static let unimplemented = Self(code: .unimplemented) - - /// Internal errors. Means some invariants expected by underlying System has - /// been broken. If you see one of these errors, Something is very broken. - public static let internalError = Self(code: .internalError) - - /// The service is currently unavailable. This is a most likely a transient - /// condition and may be corrected by retrying with a backoff. - /// - /// See litmus test above for deciding between ``failedPrecondition``, ``aborted``, - /// and ``unavailable``. - public static let unavailable = Self(code: .unavailable) - - /// Unrecoverable data loss or corruption. - public static let dataLoss = Self(code: .dataLoss) - - /// The request does not have valid authentication credentials for the - /// operation. - public static let unauthenticated = Self(code: .unauthenticated) -} diff --git a/Sources/GRPCCore/RuntimeError.swift b/Sources/GRPCCore/RuntimeError.swift deleted file mode 100644 index 357aa59f7..000000000 --- a/Sources/GRPCCore/RuntimeError.swift +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -/// An error thrown at runtime. -/// -/// In contrast to ``RPCError``, the ``RuntimeError`` represents errors which happen at a scope -/// wider than an individual RPC. For example, passing invalid configuration values. -public struct RuntimeError: Error, Hashable, Sendable { - /// The code indicating the domain of the error. - public var code: Code - - /// A message providing more details about the error which may include details specific to this - /// instance of the error. - public var message: String - - /// The original error which led to this error being thrown. - public var cause: (any Error)? - - /// Creates a new error. - /// - /// - Parameters: - /// - code: The error code. - /// - message: A description of the error. - /// - cause: The original error which led to this error being thrown. - public init(code: Code, message: String, cause: (any Error)? = nil) { - self.code = code - self.message = message - self.cause = cause - } - - public func hash(into hasher: inout Hasher) { - hasher.combine(self.code) - hasher.combine(self.message) - } - - public static func == (lhs: Self, rhs: Self) -> Bool { - return lhs.code == rhs.code && lhs.message == rhs.message - } -} - -extension RuntimeError: CustomStringConvertible { - public var description: String { - if let cause = self.cause { - return "\(self.code): \"\(self.message)\" (cause: \"\(cause)\")" - } else { - return "\(self.code): \"\(self.message)\"" - } - } -} - -extension RuntimeError { - public struct Code: Hashable, Sendable { - private enum Value { - case invalidArgument - case serverIsAlreadyRunning - case serverIsStopped - case clientIsAlreadyRunning - case clientIsStopped - case transportError - } - - private var value: Value - private init(_ value: Value) { - self.value = value - } - - /// An argument was invalid. - public static var invalidArgument: Self { - Self(.invalidArgument) - } - - /// At attempt to start the server was made but it is already running. - public static var serverIsAlreadyRunning: Self { - Self(.serverIsAlreadyRunning) - } - - /// At attempt to start the server was made but it has already stopped. - public static var serverIsStopped: Self { - Self(.serverIsStopped) - } - - /// At attempt to start the client was made but it is already running. - public static var clientIsAlreadyRunning: Self { - Self(.clientIsAlreadyRunning) - } - - /// At attempt to start the client was made but it has already stopped. - public static var clientIsStopped: Self { - Self(.clientIsStopped) - } - - /// The transport threw an error whilst connected. - public static var transportError: Self { - Self(.transportError) - } - } -} - -extension RuntimeError.Code: CustomStringConvertible { - public var description: String { - String(describing: self.value) - } -} diff --git a/Sources/GRPCCore/ServiceDescriptor.swift b/Sources/GRPCCore/ServiceDescriptor.swift deleted file mode 100644 index b09730c3b..000000000 --- a/Sources/GRPCCore/ServiceDescriptor.swift +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -/// A description of a service. -public struct ServiceDescriptor: Sendable, Hashable { - /// The name of the package the service belongs to. For example, "helloworld". - /// An empty string means that the service does not belong to any package. - public var package: String - - /// The name of the service. For example, "Greeter". - public var service: String - - /// The fully qualified service name in the format: - /// - "package.service": if a package name is specified. For example, "helloworld.Greeter". - /// - "service": if a package name is not specified. For example, "Greeter". - public var fullyQualifiedService: String { - if self.package.isEmpty { - return self.service - } - - return "\(self.package).\(self.service)" - } - - /// - Parameters: - /// - package: The name of the package the service belongs to. For example, "helloworld". - /// An empty string means that the service does not belong to any package. - /// - service: The name of the service. For example, "Greeter". - public init(package: String, service: String) { - self.package = package - self.service = service - } -} diff --git a/Sources/GRPCCore/Status.swift b/Sources/GRPCCore/Status.swift deleted file mode 100644 index ed6636b7f..000000000 --- a/Sources/GRPCCore/Status.swift +++ /dev/null @@ -1,298 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -/// A status object represents the outcome of an RPC. -/// -/// Each ``Status`` is composed of a ``Status/code-swift.property`` and ``Status/message``. Each -/// service implementation chooses the code and message returned to the client for each RPC -/// it implements. However, client and server implementations may also generate status objects -/// on their own if an error happens. -/// -/// ``Status`` represents the raw outcome of an RPC whether it was successful or not; ``RPCError`` -/// is similar to ``Status`` but only represents error cases, in other words represents all status -/// codes apart from ``Code-swift.struct/ok``. -public struct Status: @unchecked Sendable, Hashable { - // @unchecked because it relies on heap allocated storage and 'isKnownUniquelyReferenced' - - private var storage: Storage - private mutating func ensureStorageIsUnique() { - if !isKnownUniquelyReferenced(&self.storage) { - self.storage = self.storage.copy() - } - } - - /// A code representing the high-level domain of the status. - public var code: Code { - get { self.storage.code } - set { - self.ensureStorageIsUnique() - self.storage.code = newValue - } - } - - /// A message providing additional context about the status. - public var message: String { - get { self.storage.message } - set { - self.ensureStorageIsUnique() - self.storage.message = newValue - } - } - - /// Create a new status. - /// - /// - Parameters: - /// - code: The status code. - /// - message: A message providing additional context about the code. - public init(code: Code, message: String) { - if code == .ok, message.isEmpty { - // Avoid a heap allocation for the common case. - self = .ok - } else { - self.storage = Storage(code: code, message: message) - } - } - - private init(storage: Storage) { - self.storage = storage - } - - /// A status with code ``Code-swift.struct/ok`` and an empty message. - @usableFromInline - internal static let ok = Status(storage: Storage(code: .ok, message: "")) -} - -extension Status: CustomStringConvertible { - public var description: String { - "\(self.code): \"\(self.message)\"" - } -} - -extension Status { - private final class Storage: Hashable { - var code: Status.Code - var message: String - - init(code: Status.Code, message: String) { - self.code = code - self.message = message - } - - func copy() -> Self { - Self(code: self.code, message: self.message) - } - - func hash(into hasher: inout Hasher) { - hasher.combine(self.code) - hasher.combine(self.message) - } - - static func == (lhs: Status.Storage, rhs: Status.Storage) -> Bool { - return lhs.code == rhs.code && lhs.message == rhs.message - } - } -} - -extension Status { - /// Status codes for gRPC operations. - /// - /// The outcome of every RPC is indicated by a status code. - public struct Code: Hashable, CustomStringConvertible, Sendable { - // Source: https://github.com/grpc/grpc/blob/master/doc/statuscodes.md - enum Wrapped: UInt8, Hashable, Sendable { - case ok = 0 - case cancelled = 1 - case unknown = 2 - case invalidArgument = 3 - case deadlineExceeded = 4 - case notFound = 5 - case alreadyExists = 6 - case permissionDenied = 7 - case resourceExhausted = 8 - case failedPrecondition = 9 - case aborted = 10 - case outOfRange = 11 - case unimplemented = 12 - case internalError = 13 - case unavailable = 14 - case dataLoss = 15 - case unauthenticated = 16 - } - - /// The underlying value. - let wrapped: Wrapped - - /// The numeric value of the error code. - public var rawValue: Int { Int(self.wrapped.rawValue) } - - /// Creates a status codes from its raw value. - /// - /// - Parameters: - /// - rawValue: The numeric value to create the code from. - /// Returns `nil` if the `rawValue` isn't a valid error code. - public init?(rawValue: Int) { - if let value = UInt8(exactly: rawValue), let wrapped = Wrapped(rawValue: value) { - self.wrapped = wrapped - } else { - return nil - } - } - - /// Creates a status code from an ``RPCError/Code-swift.struct``. - /// - /// - Parameters: - /// - code: The error code to create this ``Status/Code-swift.struct`` from. - public init(_ code: RPCError.Code) { - self.wrapped = code.wrapped - } - - private init(code: Wrapped) { - self.wrapped = code - } - - public var description: String { - String(describing: self.wrapped) - } - - package static let all: [Self] = [ - .ok, - .cancelled, - .unknown, - .invalidArgument, - .deadlineExceeded, - .notFound, - .alreadyExists, - .permissionDenied, - .resourceExhausted, - .failedPrecondition, - .aborted, - .outOfRange, - .unimplemented, - .internalError, - .unavailable, - .dataLoss, - .unauthenticated, - ] - } -} - -extension Status.Code { - /// The operation completed successfully. - public static let ok = Self(code: .ok) - - /// The operation was cancelled (typically by the caller). - public static let cancelled = Self(code: .cancelled) - - /// Unknown error. An example of where this error may be returned is if a - /// Status value received from another address space belongs to an error-space - /// that is not known in this address space. Also errors raised by APIs that - /// do not return enough error information may be converted to this error. - public static let unknown = Self(code: .unknown) - - /// Client specified an invalid argument. Note that this differs from - /// ``failedPrecondition``. ``invalidArgument`` indicates arguments that are - /// problematic regardless of the state of the system (e.g., a malformed file - /// name). - public static let invalidArgument = Self(code: .invalidArgument) - - /// Deadline expired before operation could complete. For operations that - /// change the state of the system, this error may be returned even if the - /// operation has completed successfully. For example, a successful response - /// from a server could have been delayed long enough for the deadline to - /// expire. - public static let deadlineExceeded = Self(code: .deadlineExceeded) - - /// Some requested entity (e.g., file or directory) was not found. - public static let notFound = Self(code: .notFound) - - /// Some entity that we attempted to create (e.g., file or directory) already - /// exists. - public static let alreadyExists = Self(code: .alreadyExists) - - /// The caller does not have permission to execute the specified operation. - /// ``permissionDenied`` must not be used for rejections caused by exhausting - /// some resource (use ``resourceExhausted`` instead for those errors). - /// ``permissionDenied`` must not be used if the caller can not be identified - /// (use ``unauthenticated`` instead for those errors). - public static let permissionDenied = Self(code: .permissionDenied) - - /// Some resource has been exhausted, perhaps a per-user quota, or perhaps the - /// entire file system is out of space. - public static let resourceExhausted = Self(code: .resourceExhausted) - - /// Operation was rejected because the system is not in a state required for - /// the operation's execution. For example, directory to be deleted may be - /// non-empty, an rmdir operation is applied to a non-directory, etc. - /// - /// A litmus test that may help a service implementor in deciding - /// between ``failedPrecondition``, ``aborted``, and ``unavailable``: - /// - Use ``unavailable`` if the client can retry just the failing call. - /// - Use ``aborted`` if the client should retry at a higher-level - /// (e.g., restarting a read-modify-write sequence). - /// - Use ``failedPrecondition`` if the client should not retry until - /// the system state has been explicitly fixed. E.g., if an "rmdir" - /// fails because the directory is non-empty, ``failedPrecondition`` - /// should be returned since the client should not retry unless - /// they have first fixed up the directory by deleting files from it. - /// - Use ``failedPrecondition`` if the client performs conditional - /// REST Get/Update/Delete on a resource and the resource on the - /// server does not match the condition. E.g., conflicting - /// read-modify-write on the same resource. - public static let failedPrecondition = Self(code: .failedPrecondition) - - /// The operation was aborted, typically due to a concurrency issue like - /// sequencer check failures, transaction aborts, etc. - /// - /// See litmus test above for deciding between ``failedPrecondition``, ``aborted``, - /// and ``unavailable``. - public static let aborted = Self(code: .aborted) - - /// Operation was attempted past the valid range. E.g., seeking or reading - /// past end of file. - /// - /// Unlike ``invalidArgument``, this error indicates a problem that may be fixed - /// if the system state changes. For example, a 32-bit file system will - /// generate ``invalidArgument`` if asked to read at an offset that is not in the - /// range [0,2^32-1], but it will generate ``outOfRange`` if asked to read from - /// an offset past the current file size. - /// - /// There is a fair bit of overlap between ``failedPrecondition`` and - /// ``outOfRange``. We recommend using ``outOfRange`` (the more specific error) - /// when it applies so that callers who are iterating through a space can - /// easily look for an ``outOfRange`` error to detect when they are done. - public static let outOfRange = Self(code: .outOfRange) - - /// Operation is not implemented or not supported/enabled in this service. - public static let unimplemented = Self(code: .unimplemented) - - /// Internal errors. Means some invariants expected by underlying System has - /// been broken. If you see one of these errors, Something is very broken. - public static let internalError = Self(code: .internalError) - - /// The service is currently unavailable. This is a most likely a transient - /// condition and may be corrected by retrying with a backoff. - /// - /// See litmus test above for deciding between ``failedPrecondition``, ``aborted``, - /// and ``unavailable``. - public static let unavailable = Self(code: .unavailable) - - /// Unrecoverable data loss or corruption. - public static let dataLoss = Self(code: .dataLoss) - - /// The request does not have valid authentication credentials for the - /// operation. - public static let unauthenticated = Self(code: .unauthenticated) -} diff --git a/Sources/GRPCCore/Streaming/Internal/AsyncSequenceOfOne.swift b/Sources/GRPCCore/Streaming/Internal/AsyncSequenceOfOne.swift deleted file mode 100644 index c0a72e176..000000000 --- a/Sources/GRPCCore/Streaming/Internal/AsyncSequenceOfOne.swift +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension RPCAsyncSequence { - /// Returns an ``RPCAsyncSequence`` containing just the given element. - @inlinable - static func one(_ element: Element) -> Self { - let source = AsyncSequenceOfOne(result: .success(element)) - return RPCAsyncSequence(wrapping: source) - } - - /// Returns an ``RPCAsyncSequence`` throwing the given error. - @inlinable - static func throwing(_ error: Failure) -> Self { - let source = AsyncSequenceOfOne(result: .failure(error)) - return RPCAsyncSequence(wrapping: source) - } -} - -/// An `AsyncSequence` of a single value. -@usableFromInline -@available(macOS 10.15, iOS 13.0, tvOS 13, watchOS 6, *) -struct AsyncSequenceOfOne: AsyncSequence, Sendable { - @usableFromInline - let result: Result - - @inlinable - init(result: Result) { - self.result = result - } - - @inlinable - func makeAsyncIterator() -> AsyncIterator { - AsyncIterator(result: self.result) - } - - @usableFromInline - struct AsyncIterator: AsyncIteratorProtocol { - @usableFromInline - private(set) var result: Result? - - @inlinable - init(result: Result) { - self.result = result - } - - @inlinable - mutating func next( - isolation actor: isolated (any Actor)? - ) async throws(Failure) -> Element? { - guard let result = self.result else { return nil } - - self.result = nil - return try result.get() - } - - @inlinable - mutating func next() async throws -> Element? { - try await self.next(isolation: nil) - } - } -} diff --git a/Sources/GRPCCore/Streaming/Internal/BroadcastAsyncSequence+RPCWriter.swift b/Sources/GRPCCore/Streaming/Internal/BroadcastAsyncSequence+RPCWriter.swift deleted file mode 100644 index ff54d9814..000000000 --- a/Sources/GRPCCore/Streaming/Internal/BroadcastAsyncSequence+RPCWriter.swift +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -extension BroadcastAsyncSequence.Source: ClosableRPCWriterProtocol { - @inlinable - func write(contentsOf elements: some Sequence) async throws { - for element in elements { - try await self.write(element) - } - } -} diff --git a/Sources/GRPCCore/Streaming/Internal/BroadcastAsyncSequence.swift b/Sources/GRPCCore/Streaming/Internal/BroadcastAsyncSequence.swift deleted file mode 100644 index 5f812eb11..000000000 --- a/Sources/GRPCCore/Streaming/Internal/BroadcastAsyncSequence.swift +++ /dev/null @@ -1,1799 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -public import DequeModule // should be @usableFromInline - -/// An `AsyncSequence` which can broadcast its values to multiple consumers concurrently. -/// -/// The sequence is not a general-purpose broadcast sequence; it is tailored specifically for the -/// requirements of gRPC Swift, in particular it is used to support retrying and hedging requests. -/// -/// In order to achieve this it maintains on an internal buffer of elements which is limited in -/// size. Each iterator ("subscriber") maintains an offset into the elements which the sequence has -/// produced over time. If a subscriber is consuming too slowly (and the buffer is full) then the -/// sequence will cancel the subscriber's subscription to the stream, dropping the oldest element -/// in the buffer to make space for more elements. If the buffer is full and all subscribers are -/// equally slow then all producers are suspended until the buffer drops to a reasonable size. -/// -/// The expectation is that the number of subscribers will be low; for retries there will be at most -/// one subscriber at a time, for hedging there may be at most five subscribers at a time. -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -@usableFromInline -struct BroadcastAsyncSequence: Sendable, AsyncSequence { - @usableFromInline - let _storage: _BroadcastSequenceStorage - - @inlinable - init(_storage: _BroadcastSequenceStorage) { - self._storage = _storage - } - - /// Make a new stream and continuation. - /// - /// - Parameters: - /// - elementType: The type of element this sequence produces. - /// - bufferSize: The number of elements this sequence may store. - /// - Returns: A stream and continuation. - @inlinable - static func makeStream( - of elementType: Element.Type = Element.self, - bufferSize: Int - ) -> (stream: Self, continuation: Self.Source) { - let storage = _BroadcastSequenceStorage(bufferSize: bufferSize) - let stream = Self(_storage: storage) - let continuation = Self.Source(_storage: storage) - return (stream, continuation) - } - - @inlinable - func makeAsyncIterator() -> AsyncIterator { - let id = self._storage.subscribe() - return AsyncIterator(_storage: _storage, id: id) - } - - /// Returns true if it is known to be safe for the next subscriber to subscribe and successfully - /// consume elements. - /// - /// This function can return `false` if there are active subscribers or the internal buffer no - /// longer contains the first element in the sequence. - @inlinable - var isKnownSafeForNextSubscriber: Bool { - self._storage.isKnownSafeForNextSubscriber - } - - /// Invalidates all active subscribers. - /// - /// Any active subscriber will receive an error the next time they attempt to consume an element. - @inlinable - func invalidateAllSubscriptions() { - self._storage.invalidateAllSubscriptions() - } -} - -// MARK: - AsyncIterator - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -extension BroadcastAsyncSequence { - @usableFromInline - struct AsyncIterator: AsyncIteratorProtocol { - @usableFromInline - let _storage: _BroadcastSequenceStorage - @usableFromInline - let _subscriberID: _BroadcastSequenceStateMachine.Subscriptions.ID - - @inlinable - init( - _storage: _BroadcastSequenceStorage, - id: _BroadcastSequenceStateMachine.Subscriptions.ID - ) { - self._storage = _storage - self._subscriberID = id - } - - @inlinable - mutating func next() async throws -> Element? { - try await self._storage.nextElement(forSubscriber: self._subscriberID) - } - } -} - -// MARK: - Continuation - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -extension BroadcastAsyncSequence { - @usableFromInline - struct Source: Sendable { - @usableFromInline - let _storage: _BroadcastSequenceStorage - - @usableFromInline - init(_storage: _BroadcastSequenceStorage) { - self._storage = _storage - } - - @inlinable - func write(_ element: Element) async throws { - try await self._storage.yield(element) - } - - @inlinable - func finish(with result: Result) { - self._storage.finish(result) - } - - @inlinable - func finish() { - self.finish(with: .success(())) - } - - @inlinable - func finish(throwing error: any Error) { - self.finish(with: .failure(error)) - } - } -} - -@usableFromInline -enum BroadcastAsyncSequenceError: Error { - /// The consumer was too slow. - case consumingTooSlow - /// The producer has already finished. - case productionAlreadyFinished -} - -// MARK: - Storage - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -@usableFromInline -final class _BroadcastSequenceStorage: Sendable { - @usableFromInline - let _state: LockedValueBox<_BroadcastSequenceStateMachine> - - @inlinable - init(bufferSize: Int) { - self._state = LockedValueBox(_BroadcastSequenceStateMachine(bufferSize: bufferSize)) - } - - deinit { - let onDrop = self._state.withLockedValue { state in - state.dropResources() - } - - switch onDrop { - case .none: - () - case .resume(let consumers, let producers): - consumers.resume() - producers.resume() - } - } - - // MARK - Producer - - /// Yield a single element to the stream. Suspends if the stream's buffer is full. - /// - /// - Parameter element: The element to write. - @inlinable - func yield(_ element: Element) async throws { - let onYield = self._state.withLockedValue { state in state.yield(element) } - - switch onYield { - case .none: - () - - case .resume(let continuations): - continuations.resume() - - case .suspend(let token): - try await withTaskCancellationHandler { - try await withCheckedThrowingContinuation { continuation in - let onProduceMore = self._state.withLockedValue { state in - state.waitToProduceMore(continuation: continuation, token: token) - } - - switch onProduceMore { - case .resume(let continuation, let result): - continuation.resume(with: result) - case .none: - () - } - } - } onCancel: { - let onCancel = self._state.withLockedValue { state in - state.cancelProducer(withToken: token) - } - - switch onCancel { - case .resume(let continuation, let result): - continuation.resume(with: result) - case .none: - () - } - } - - case .throwAlreadyFinished: - throw BroadcastAsyncSequenceError.productionAlreadyFinished - } - } - - /// Indicate that no more values will be produced. - /// - /// - Parameter result: Whether the stream is finishing cleanly or because of an error. - @inlinable - func finish(_ result: Result) { - let action = self._state.withLockedValue { state in state.finish(result: result) } - switch action { - case .none: - () - case .resume(let subscribers, let producers): - subscribers.resume() - producers.resume() - } - } - - // MARK: - Consumer - - /// Create a subscription to the stream. - /// - /// - Returns: Returns a unique subscription ID. - @inlinable - func subscribe() -> _BroadcastSequenceStateMachine.Subscriptions.ID { - self._state.withLockedValue { $0.subscribe() } - } - - /// Returns the next element for the given subscriber, if it is available. - /// - /// - Parameter id: The ID of the subscriber requesting the element. - /// - Returns: The next element or `nil` if the stream has been terminated. - @inlinable - func nextElement( - forSubscriber id: _BroadcastSequenceStateMachine.Subscriptions.ID - ) async throws -> Element? { - return try await withTaskCancellationHandler { - self._state.unsafe.lock() - let onNext = self._state.unsafe.withValueAssumingLockIsAcquired { - $0.nextElement(forSubscriber: id) - } - - switch onNext { - case .return(let returnAndProduceMore): - self._state.unsafe.unlock() - returnAndProduceMore.producers.resume() - return try returnAndProduceMore.nextResult.get() - - case .suspend: - return try await withCheckedThrowingContinuation { continuation in - let onSetContinuation = self._state.unsafe.withValueAssumingLockIsAcquired { state in - state.setContinuation(continuation, forSubscription: id) - } - - self._state.unsafe.unlock() - - switch onSetContinuation { - case .resume(let continuation, let result): - continuation.resume(with: result) - case .none: - () - } - } - } - } onCancel: { - let onCancel = self._state.withLockedValue { state in - state.cancelSubscription(withID: id) - } - - switch onCancel { - case .resume(let continuation, let result): - continuation.resume(with: result) - case .none: - () - } - } - } - - /// Returns true if it's guaranteed that the next subscriber may join and safely begin consuming - /// elements. - @inlinable - var isKnownSafeForNextSubscriber: Bool { - self._state.withLockedValue { state in - state.nextSubscriptionIsValid - } - } - - /// Invalidates all active subscriptions. - @inlinable - func invalidateAllSubscriptions() { - let action = self._state.withLockedValue { state in - state.invalidateAllSubscriptions() - } - - switch action { - case .resume(let continuations): - continuations.resume() - case .none: - () - } - } -} - -// MARK: - State machine - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -@usableFromInline -struct _BroadcastSequenceStateMachine: Sendable { - @usableFromInline - typealias ConsumerContinuation = CheckedContinuation - @usableFromInline - typealias ProducerContinuation = CheckedContinuation - - @usableFromInline - struct ConsumerContinuations { - @usableFromInline - var continuations: _OneOrMany - @usableFromInline - var result: Result - - @inlinable - init(continuations: _OneOrMany, result: Result) { - self.continuations = continuations - self.result = result - } - - @inlinable - func resume() { - switch self.continuations { - case .one(let continuation): - continuation.resume(with: self.result) - case .many(let continuations): - for continuation in continuations { - continuation.resume(with: self.result) - } - } - } - } - - @usableFromInline - struct ProducerContinuations { - @usableFromInline - var continuations: [ProducerContinuation] - @usableFromInline - var result: Result - - @inlinable - init(continuations: [ProducerContinuation], result: Result) { - self.continuations = continuations - self.result = result - } - - @inlinable - func resume() { - for continuation in self.continuations { - continuation.resume(with: self.result) - } - } - } - - @usableFromInline - enum State: Sendable { - /// No subscribers and no elements have been produced. - case initial(Initial) - /// Subscribers exist but no elements have been produced. - case subscribed(Subscribed) - /// Elements have been produced, there may or may not be subscribers. - case streaming(Streaming) - /// No more elements will be produced. There may or may not been subscribers. - case finished(Finished) - /// Temporary state to avoid CoWs. - case _modifying - - @inlinable - init(bufferSize: Int) { - self = .initial(Initial(bufferSize: bufferSize)) - } - - @usableFromInline - struct Initial: Sendable { - @usableFromInline - let bufferSize: Int - - @inlinable - init(bufferSize: Int) { - self.bufferSize = bufferSize - } - } - - @usableFromInline - struct Subscribed: Sendable { - /// Active subscriptions. - @usableFromInline - var subscriptions: _BroadcastSequenceStateMachine.Subscriptions - /// Subscriptions to fail and remove when they next request an element. - @usableFromInline - var subscriptionsToDrop: [_BroadcastSequenceStateMachine.Subscriptions.ID] - - /// The maximum size of the element buffer. - @usableFromInline - let bufferSize: Int - - @inlinable - init(from state: Initial) { - self.subscriptions = Subscriptions() - self.subscriptionsToDrop = [] - self.bufferSize = state.bufferSize - } - - @inlinable - mutating func finish(result: Result) -> OnFinish { - let continuations = self.subscriptions.removeSubscribersWithContinuations() - return .resume( - .init(continuations: continuations, result: result.map { nil }), - .init(continuations: [], result: .success(())) - ) - } - - @inlinable - mutating func next(_ id: _BroadcastSequenceStateMachine.Subscriptions.ID) -> OnNext { - // Not streaming, so suspend or remove if the subscription should be dropped. - guard let index = self.subscriptionsToDrop.firstIndex(of: id) else { - return .suspend - } - - self.subscriptionsToDrop.remove(at: index) - return .return(.init(nextResult: .failure(BroadcastAsyncSequenceError.consumingTooSlow))) - } - - @inlinable - mutating func cancel( - _ id: _BroadcastSequenceStateMachine.Subscriptions.ID - ) -> OnCancelSubscription { - let (_, continuation) = self.subscriptions.removeSubscriber(withID: id) - if let continuation = continuation { - return .resume(continuation, .failure(CancellationError())) - } else { - return .none - } - } - - @inlinable - mutating func setContinuation( - _ continuation: ConsumerContinuation, - forSubscription id: _BroadcastSequenceStateMachine.Subscriptions.ID - ) -> OnSetContinuation { - if self.subscriptions.setContinuation(continuation, forSubscriber: id) { - return .none - } else { - return .resume(continuation, .failure(CancellationError())) - } - } - - @inlinable - mutating func subscribe() -> _BroadcastSequenceStateMachine.Subscriptions.ID { - self.subscriptions.subscribe() - } - - @inlinable - mutating func invalidateAllSubscriptions() -> OnInvalidateAllSubscriptions { - // Remove subscriptions with continuations, they need to be failed. - let continuations = self.subscriptions.removeSubscribersWithContinuations() - let consumerContinuations = ConsumerContinuations( - continuations: continuations, - result: .failure(BroadcastAsyncSequenceError.consumingTooSlow) - ) - - // Remove any others to be failed when they next call 'next'. - let ids = self.subscriptions.removeAllSubscribers() - self.subscriptionsToDrop.append(contentsOf: ids) - return .resume(consumerContinuations) - } - - @inlinable - mutating func dropResources(error: BroadcastAsyncSequenceError) -> OnDropResources { - let continuations = self.subscriptions.removeSubscribersWithContinuations() - let consumerContinuations = ConsumerContinuations( - continuations: continuations, - result: .failure(error) - ) - let producerContinuations = ProducerContinuations(continuations: [], result: .success(())) - return .resume(consumerContinuations, producerContinuations) - } - } - - @usableFromInline - struct Streaming: Sendable { - /// A deque of elements tagged with IDs. - @usableFromInline - var elements: Elements - /// The maximum size of the element buffer. - @usableFromInline - let bufferSize: Int - - // TODO: (optimisation) one-or-many Deque to avoid allocations in the case of a single writer - /// Producers which have been suspended. - @usableFromInline - var producers: [(ProducerContinuation, Int)] - /// The IDs of producers which have been cancelled. - @usableFromInline - var cancelledProducers: [Int] - /// The next token for a producer. - @usableFromInline - var producerToken: Int - - /// Active subscriptions. - @usableFromInline - var subscriptions: _BroadcastSequenceStateMachine.Subscriptions - /// Subscriptions to fail and remove when they next request an element. - @usableFromInline - var subscriptionsToDrop: [_BroadcastSequenceStateMachine.Subscriptions.ID] - @inlinable - init(from state: Initial) { - self.elements = Elements() - self.producers = [] - self.producerToken = 0 - self.cancelledProducers = [] - self.subscriptions = Subscriptions() - self.subscriptionsToDrop = [] - self.bufferSize = state.bufferSize - } - - @inlinable - init(from state: Subscribed) { - self.elements = Elements() - self.producers = [] - self.producerToken = 0 - self.cancelledProducers = [] - self.subscriptions = state.subscriptions - self.subscriptionsToDrop = state.subscriptionsToDrop - self.bufferSize = state.bufferSize - } - - @inlinable - mutating func append(_ element: Element) -> OnYield { - let onYield: OnYield - self.elements.append(element) - - if self.elements.count >= self.bufferSize, let lowestID = self.elements.lowestID { - // If the buffer is too large then: - // - if all subscribers are equally slow suspend the producer - // - if some subscribers are slow then remove them and the oldest value - // - if no subscribers are slow then remove the oldest value - let slowConsumers = self.subscriptions.subscribers(withElementID: lowestID) - - switch slowConsumers.count { - case 0: - if self.subscriptions.isEmpty { - // No consumers. - let token = self.producerToken - self.producerToken += 1 - onYield = .suspend(token) - } else { - // No consumers are slow, some subscribers exist, a subset of which are waiting - // for a value. Drop the oldest value and resume the fastest consumers. - self.elements.removeFirst() - let continuations = self.subscriptions.takeContinuations().map { - ConsumerContinuations(continuations: $0, result: .success(element)) - } - - if let continuations = continuations { - onYield = .resume(continuations) - } else { - onYield = .none - } - } - - case self.subscriptions.count: - // All consumers are slow; stop the production of new value. - let token = self.producerToken - self.producerToken += 1 - onYield = .suspend(token) - - default: - // Some consumers are slow, but not all. Remove the slow consumers and drop the - // oldest value. - self.elements.removeFirst() - self.subscriptions.removeAllSubscribers(in: slowConsumers) - self.subscriptionsToDrop.append(contentsOf: slowConsumers) - onYield = .none - } - } else { - // The buffer isn't full. Take the continuations of subscriptions which have them; they - // must be waiting for the value we just appended. - let continuations = self.subscriptions.takeContinuations().map { - ConsumerContinuations(continuations: $0, result: .success(element)) - } - - if let continuations = continuations { - onYield = .resume(continuations) - } else { - onYield = .none - } - } - - return onYield - } - - @inlinable - mutating func next(_ id: _BroadcastSequenceStateMachine.Subscriptions.ID) -> OnNext { - let onNext: OnNext - - // 1. Lookup the subscriber by ID to get their next offset - // 2. If the element exists, update the element pointer and return the element - // 3. Else if the ID is in the future, wait - // 4. Else the ID is in the past, fail and remove the subscription. - - // Lookup the subscriber with the given ID. - let onNextForSubscription = self.subscriptions.withMutableElementID( - forSubscriber: id - ) { elementID -> (OnNext, Bool) in - let onNext: OnNext - let removeSubscription: Bool - - // Subscriber exists; do we have the element it requires next? - switch self.elements.lookupElement(withID: elementID) { - case .found(let element): - // Element exists in the buffer. Advance our element ID. - elementID.formNext() - onNext = .return(.init(nextResult: .success(element))) - removeSubscription = false - case .maybeAvailableLater: - // Element may exist in the future. - onNext = .suspend - removeSubscription = false - case .noLongerAvailable: - // Element existed in the past but was dropped from the buffer. - onNext = .return( - .init(nextResult: .failure(BroadcastAsyncSequenceError.consumingTooSlow)) - ) - removeSubscription = true - } - - return (onNext, removeSubscription) - } - - switch onNextForSubscription { - case .return(var resultAndResume): - // The producer only suspends when all consumers are equally slow or there are no - // consumers at all. The latter can't be true: this function can only be called by a - // consumer. The former can't be true anymore because consumption isn't concurrent - // so this consumer must be faster than the others so let the producer resume. - // - // Note that this doesn't mean that all other consumers will be dropped: they can continue - // to produce until the producer provides more values. - resultAndResume.producers = ProducerContinuations( - continuations: self.producers.map { $0.0 }, - result: .success(()) - ) - self.producers.removeAll() - onNext = .return(resultAndResume) - - case .suspend: - onNext = .suspend - - case .none: - // No subscription found, must have been dropped or already finished. - if let index = self.subscriptionsToDrop.firstIndex(where: { $0 == id }) { - self.subscriptionsToDrop.remove(at: index) - onNext = .return( - .init(nextResult: .failure(BroadcastAsyncSequenceError.consumingTooSlow)) - ) - } else { - // Unknown subscriber, i.e. already finished. - onNext = .return(.init(nextResult: .success(nil))) - } - } - - return onNext - } - - @inlinable - mutating func setContinuation( - _ continuation: ConsumerContinuation, - forSubscription id: _BroadcastSequenceStateMachine.Subscriptions.ID - ) -> OnSetContinuation { - if self.subscriptions.setContinuation(continuation, forSubscriber: id) { - return .none - } else { - return .resume(continuation, .failure(CancellationError())) - } - } - - @inlinable - mutating func cancel( - _ id: _BroadcastSequenceStateMachine.Subscriptions.ID - ) -> OnCancelSubscription { - let (_, continuation) = self.subscriptions.removeSubscriber(withID: id) - if let continuation = continuation { - return .resume(continuation, .failure(CancellationError())) - } else { - return .none - } - } - - @inlinable - mutating func waitToProduceMore( - _ continuation: ProducerContinuation, - token: Int - ) -> OnWaitToProduceMore { - let onWaitToProduceMore: OnWaitToProduceMore - - if self.elements.count < self.bufferSize { - // Buffer has free space, no need to suspend. - onWaitToProduceMore = .resume(continuation, .success(())) - } else if let index = self.cancelledProducers.firstIndex(of: token) { - // Producer was cancelled before suspending. - self.cancelledProducers.remove(at: index) - onWaitToProduceMore = .resume(continuation, .failure(CancellationError())) - } else { - // Store the continuation to resume later. - self.producers.append((continuation, token)) - onWaitToProduceMore = .none - } - - return onWaitToProduceMore - } - - @inlinable - mutating func cancelProducer(withToken token: Int) -> OnCancelProducer { - guard let index = self.producers.firstIndex(where: { $0.1 == token }) else { - self.cancelledProducers.append(token) - return .none - } - - let (continuation, _) = self.producers.remove(at: index) - return .resume(continuation, .failure(CancellationError())) - } - - @inlinable - mutating func finish(result: Result) -> OnFinish { - let continuations = self.subscriptions.removeSubscribersWithContinuations() - let producers = self.producers.map { $0.0 } - self.producers.removeAll() - return .resume( - .init(continuations: continuations, result: result.map { nil }), - .init(continuations: producers, result: .success(())) - ) - } - - @inlinable - mutating func subscribe() -> _BroadcastSequenceStateMachine.Subscriptions.ID { - self.subscriptions.subscribe() - } - - @inlinable - mutating func invalidateAllSubscriptions() -> OnInvalidateAllSubscriptions { - // Remove subscriptions with continuations, they need to be failed. - let continuations = self.subscriptions.removeSubscribersWithContinuations() - let consumerContinuations = ConsumerContinuations( - continuations: continuations, - result: .failure(BroadcastAsyncSequenceError.consumingTooSlow) - ) - - // Remove any others to be failed when they next call 'next'. - let ids = self.subscriptions.removeAllSubscribers() - self.subscriptionsToDrop.append(contentsOf: ids) - return .resume(consumerContinuations) - } - - @inlinable - mutating func dropResources(error: BroadcastAsyncSequenceError) -> OnDropResources { - let continuations = self.subscriptions.removeSubscribersWithContinuations() - let consumerContinuations = ConsumerContinuations( - continuations: continuations, - result: .failure(error) - ) - - let producers = ProducerContinuations( - continuations: self.producers.map { $0.0 }, - result: .failure(error) - ) - - self.producers.removeAll() - - return .resume(consumerContinuations, producers) - } - - @inlinable - func nextSubscriptionIsValid() -> Bool { - return self.subscriptions.isEmpty && self.elements.lowestID == .initial - } - } - - @usableFromInline - struct Finished: Sendable { - /// A deque of elements tagged with IDs. - @usableFromInline - var elements: Elements - - /// Active subscriptions. - @usableFromInline - var subscriptions: _BroadcastSequenceStateMachine.Subscriptions - /// Subscriptions to fail and remove when they next request an element. - @usableFromInline - var subscriptionsToDrop: [_BroadcastSequenceStateMachine.Subscriptions.ID] - - /// The terminating result of the sequence. - @usableFromInline - let result: Result - - @inlinable - init(from state: Initial, result: Result) { - self.elements = Elements() - self.subscriptions = Subscriptions() - self.subscriptionsToDrop = [] - self.result = result - } - - @inlinable - init(from state: Subscribed, result: Result) { - self.elements = Elements() - self.subscriptions = state.subscriptions - self.subscriptionsToDrop = [] - self.result = result - } - - @inlinable - init(from state: Streaming, result: Result) { - self.elements = state.elements - self.subscriptions = state.subscriptions - self.subscriptionsToDrop = state.subscriptionsToDrop - self.result = result - } - - @inlinable - mutating func next(_ id: _BroadcastSequenceStateMachine.Subscriptions.ID) -> OnNext { - let onNext: OnNext - let onNextForSubscription = self.subscriptions.withMutableElementID( - forSubscriber: id - ) { elementID -> (OnNext, Bool) in - let onNext: OnNext - let removeSubscription: Bool - - switch self.elements.lookupElement(withID: elementID) { - case .found(let element): - elementID.formNext() - onNext = .return(.init(nextResult: .success(element))) - removeSubscription = false - case .maybeAvailableLater: - onNext = .return(.init(nextResult: self.result.map { nil })) - removeSubscription = true - case .noLongerAvailable: - onNext = .return( - .init(nextResult: .failure(BroadcastAsyncSequenceError.consumingTooSlow)) - ) - removeSubscription = true - } - - return (onNext, removeSubscription) - } - - switch onNextForSubscription { - case .return(let result): - onNext = .return(result) - - case .none: - // No subscriber with the given ID, it was likely dropped previously. - if let index = self.subscriptionsToDrop.firstIndex(where: { $0 == id }) { - self.subscriptionsToDrop.remove(at: index) - onNext = .return( - .init(nextResult: .failure(BroadcastAsyncSequenceError.consumingTooSlow)) - ) - } else { - // Unknown subscriber, i.e. already finished. - onNext = .return(.init(nextResult: .success(nil))) - } - - case .suspend: - fatalError("Internal inconsistency") - } - - return onNext - } - - @inlinable - mutating func subscribe() -> _BroadcastSequenceStateMachine.Subscriptions.ID { - self.subscriptions.subscribe() - } - - @inlinable - mutating func invalidateAllSubscriptions() -> OnInvalidateAllSubscriptions { - // Remove subscriptions with continuations, they need to be failed. - let continuations = self.subscriptions.removeSubscribersWithContinuations() - let consumerContinuations = ConsumerContinuations( - continuations: continuations, - result: .failure(BroadcastAsyncSequenceError.consumingTooSlow) - ) - - // Remove any others to be failed when they next call 'next'. - let ids = self.subscriptions.removeAllSubscribers() - self.subscriptionsToDrop.append(contentsOf: ids) - return .resume(consumerContinuations) - } - - @inlinable - mutating func dropResources(error: BroadcastAsyncSequenceError) -> OnDropResources { - let continuations = self.subscriptions.removeSubscribersWithContinuations() - let consumerContinuations = ConsumerContinuations( - continuations: continuations, - result: .failure(error) - ) - - let producers = ProducerContinuations(continuations: [], result: .failure(error)) - return .resume(consumerContinuations, producers) - } - - @inlinable - func nextSubscriptionIsValid() -> Bool { - self.elements.lowestID == .initial - } - - @inlinable - mutating func cancel( - _ id: _BroadcastSequenceStateMachine.Subscriptions.ID - ) -> OnCancelSubscription { - let (_, continuation) = self.subscriptions.removeSubscriber(withID: id) - if let continuation = continuation { - return .resume(continuation, .failure(CancellationError())) - } else { - return .none - } - } - } - } - - @usableFromInline - var _state: State - - @inlinable - init(bufferSize: Int) { - self._state = State(bufferSize: bufferSize) - } - - @inlinable - var nextSubscriptionIsValid: Bool { - let isValid: Bool - - switch self._state { - case .initial: - isValid = true - case .subscribed: - isValid = true - case .streaming(let state): - isValid = state.nextSubscriptionIsValid() - case .finished(let state): - isValid = state.nextSubscriptionIsValid() - case ._modifying: - fatalError("Internal inconsistency") - } - - return isValid - } - - @usableFromInline - enum OnInvalidateAllSubscriptions { - case resume(ConsumerContinuations) - case none - } - - @inlinable - mutating func invalidateAllSubscriptions() -> OnInvalidateAllSubscriptions { - let onCancel: OnInvalidateAllSubscriptions - - switch self._state { - case .initial: - onCancel = .none - - case .subscribed(var state): - self._state = ._modifying - onCancel = state.invalidateAllSubscriptions() - self._state = .subscribed(state) - - case .streaming(var state): - self._state = ._modifying - onCancel = state.invalidateAllSubscriptions() - self._state = .streaming(state) - - case .finished(var state): - self._state = ._modifying - onCancel = state.invalidateAllSubscriptions() - self._state = .finished(state) - - case ._modifying: - fatalError("Internal inconsistency") - } - - return onCancel - } - - @usableFromInline - enum OnYield { - case none - case suspend(Int) - case resume(ConsumerContinuations) - case throwAlreadyFinished - } - - @inlinable - mutating func yield(_ element: Element) -> OnYield { - let onYield: OnYield - - switch self._state { - case .initial(let state): - self._state = ._modifying - // Move to streaming. - var state = State.Streaming(from: state) - onYield = state.append(element) - self._state = .streaming(state) - - case .subscribed(let state): - self._state = ._modifying - var state = State.Streaming(from: state) - onYield = state.append(element) - self._state = .streaming(state) - - case .streaming(var state): - self._state = ._modifying - onYield = state.append(element) - self._state = .streaming(state) - - case .finished: - onYield = .throwAlreadyFinished - - case ._modifying: - fatalError("Internal inconsistency") - } - - return onYield - } - - @usableFromInline - enum OnFinish { - case none - case resume(ConsumerContinuations, ProducerContinuations) - } - - @inlinable - mutating func finish(result: Result) -> OnFinish { - let onFinish: OnFinish - - switch self._state { - case .initial(let state): - self._state = ._modifying - let state = State.Finished(from: state, result: result) - self._state = .finished(state) - onFinish = .none - - case .subscribed(var state): - self._state = ._modifying - onFinish = state.finish(result: result) - self._state = .finished(State.Finished(from: state, result: result)) - - case .streaming(var state): - self._state = ._modifying - onFinish = state.finish(result: result) - self._state = .finished(State.Finished(from: state, result: result)) - - case .finished: - onFinish = .none - - case ._modifying: - fatalError("Internal inconsistency") - } - - return onFinish - } - - @usableFromInline - enum OnNext { - @usableFromInline - struct ReturnAndResumeProducers { - @usableFromInline - var nextResult: Result - @usableFromInline - var producers: ProducerContinuations - - @inlinable - init( - nextResult: Result, - producers: [ProducerContinuation] = [], - producerResult: Result = .success(()) - ) { - self.nextResult = nextResult - self.producers = ProducerContinuations(continuations: producers, result: producerResult) - } - } - - case `return`(ReturnAndResumeProducers) - case suspend - } - - @inlinable - mutating func nextElement( - forSubscriber id: _BroadcastSequenceStateMachine.Subscriptions.ID - ) -> OnNext { - let onNext: OnNext - - switch self._state { - case .initial: - // No subscribers so demand isn't possible. - fatalError("Internal inconsistency") - - case .subscribed(var state): - self._state = ._modifying - onNext = state.next(id) - self._state = .subscribed(state) - - case .streaming(var state): - self._state = ._modifying - onNext = state.next(id) - self._state = .streaming(state) - - case .finished(var state): - self._state = ._modifying - onNext = state.next(id) - self._state = .finished(state) - - case ._modifying: - fatalError("Internal inconsistency") - } - - return onNext - } - - @usableFromInline - enum OnSetContinuation { - case none - case resume(ConsumerContinuation, Result) - } - - @inlinable - mutating func setContinuation( - _ continuation: ConsumerContinuation, - forSubscription id: _BroadcastSequenceStateMachine.Subscriptions.ID - ) -> OnSetContinuation { - let onSetContinuation: OnSetContinuation - - switch self._state { - case .initial: - // No subscribers so demand isn't possible. - fatalError("Internal inconsistency") - - case .subscribed(var state): - self._state = ._modifying - onSetContinuation = state.setContinuation(continuation, forSubscription: id) - self._state = .subscribed(state) - - case .streaming(var state): - self._state = ._modifying - onSetContinuation = state.setContinuation(continuation, forSubscription: id) - self._state = .streaming(state) - - case .finished(let state): - onSetContinuation = .resume(continuation, state.result.map { _ in nil }) - - case ._modifying: - // All values must have been produced, nothing to wait for. - fatalError("Internal inconsistency") - } - - return onSetContinuation - } - - @usableFromInline - enum OnCancelSubscription { - case none - case resume(ConsumerContinuation, Result) - } - - @inlinable - mutating func cancelSubscription( - withID id: _BroadcastSequenceStateMachine.Subscriptions.ID - ) -> OnCancelSubscription { - let onCancel: OnCancelSubscription - - switch self._state { - case .initial: - // No subscribers so demand isn't possible. - fatalError("Internal inconsistency") - - case .subscribed(var state): - self._state = ._modifying - onCancel = state.cancel(id) - self._state = .subscribed(state) - - case .streaming(var state): - self._state = ._modifying - onCancel = state.cancel(id) - self._state = .streaming(state) - - case .finished(var state): - self._state = ._modifying - onCancel = state.cancel(id) - self._state = .finished(state) - - case ._modifying: - // All values must have been produced, nothing to wait for. - fatalError("Internal inconsistency") - } - - return onCancel - } - - @usableFromInline - enum OnSubscribe { - case subscribed(_BroadcastSequenceStateMachine.Subscriptions.ID) - } - - @inlinable - mutating func subscribe() -> _BroadcastSequenceStateMachine.Subscriptions.ID { - let id: _BroadcastSequenceStateMachine.Subscriptions.ID - - switch self._state { - case .initial(let state): - self._state = ._modifying - var state = State.Subscribed(from: state) - id = state.subscribe() - self._state = .subscribed(state) - - case .subscribed(var state): - self._state = ._modifying - id = state.subscribe() - self._state = .subscribed(state) - - case .streaming(var state): - self._state = ._modifying - id = state.subscribe() - self._state = .streaming(state) - - case .finished(var state): - self._state = ._modifying - id = state.subscribe() - self._state = .finished(state) - - case ._modifying: - fatalError("Internal inconsistency") - } - - return id - } - - @usableFromInline - enum OnWaitToProduceMore { - case none - case resume(ProducerContinuation, Result) - } - - @inlinable - mutating func waitToProduceMore( - continuation: ProducerContinuation, - token: Int - ) -> OnWaitToProduceMore { - let onWaitToProduceMore: OnWaitToProduceMore - - switch self._state { - case .initial, .subscribed: - // Nothing produced yet, so no reason have to wait to produce. - fatalError("Internal inconsistency") - - case .streaming(var state): - self._state = ._modifying - onWaitToProduceMore = state.waitToProduceMore(continuation, token: token) - self._state = .streaming(state) - - case .finished: - onWaitToProduceMore = .resume(continuation, .success(())) - - case ._modifying: - fatalError("Internal inconsistency") - } - - return onWaitToProduceMore - } - - @usableFromInline - typealias OnCancelProducer = OnWaitToProduceMore - - @inlinable - mutating func cancelProducer(withToken token: Int) -> OnCancelProducer { - let onCancelProducer: OnCancelProducer - - switch self._state { - case .initial, .subscribed: - // Nothing produced yet, so no reason have to wait to produce. - fatalError("Internal inconsistency") - - case .streaming(var state): - self._state = ._modifying - onCancelProducer = state.cancelProducer(withToken: token) - self._state = .streaming(state) - - case .finished: - // No producers to cancel; do nothing. - onCancelProducer = .none - - case ._modifying: - fatalError("Internal inconsistency") - } - - return onCancelProducer - } - - @usableFromInline - enum OnDropResources { - case none - case resume(ConsumerContinuations, ProducerContinuations) - } - - @inlinable - mutating func dropResources() -> OnDropResources { - let error = BroadcastAsyncSequenceError.productionAlreadyFinished - let onDrop: OnDropResources - - switch self._state { - case .initial(let state): - self._state = ._modifying - onDrop = .none - self._state = .finished(State.Finished(from: state, result: .failure(error))) - - case .subscribed(var state): - self._state = ._modifying - onDrop = state.dropResources(error: error) - self._state = .finished(State.Finished(from: state, result: .failure(error))) - - case .streaming(var state): - self._state = ._modifying - onDrop = state.dropResources(error: error) - self._state = .finished(State.Finished(from: state, result: .failure(error))) - - case .finished(var state): - self._state = ._modifying - onDrop = state.dropResources(error: error) - self._state = .finished(state) - - case ._modifying: - fatalError("Internal inconsistency") - } - - return onDrop - } -} - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -extension _BroadcastSequenceStateMachine { - /// A collection of elements tagged with an identifier. - /// - /// Identifiers are assigned when elements are added to the collection and are monotonically - /// increasing. If element 'A' is added before element 'B' then 'A' will have a lower ID than 'B'. - @usableFromInline - struct Elements: Sendable { - /// The ID of an element - @usableFromInline - struct ID: Hashable, Sendable, Comparable, Strideable { - @usableFromInline - private(set) var rawValue: Int - - @usableFromInline - static var initial: Self { - ID(id: 0) - } - - private init(id: Int) { - self.rawValue = id - } - - @inlinable - mutating func formNext() { - self.rawValue += 1 - } - - @inlinable - func next() -> Self { - var copy = self - copy.formNext() - return copy - } - - @inlinable - func distance(to other: Self) -> Int { - other.rawValue - self.rawValue - } - - @inlinable - func advanced(by n: Int) -> Self { - var copy = self - copy.rawValue += n - return copy - } - - @inlinable - static func < (lhs: Self, rhs: Self) -> Bool { - lhs.rawValue < rhs.rawValue - } - } - - @usableFromInline - struct _IdentifiableElement: Sendable { - @usableFromInline - var element: Element - @usableFromInline - var id: ID - - @inlinable - init(element: Element, id: ID) { - self.element = element - self.id = id - } - } - - @usableFromInline - var _elements: Deque<_IdentifiableElement> - @usableFromInline - var _nextID: ID - - @inlinable - init() { - self._nextID = .initial - self._elements = [] - } - - @inlinable - mutating func nextElementID() -> ID { - let id = self._nextID - self._nextID.formNext() - return id - } - - /// The highest ID of the stored elements; `nil` if there are no elements. - @inlinable - var highestID: ID? { self._elements.last?.id } - - /// The lowest ID of the stored elements; `nil` if there are no elements. - @inlinable - var lowestID: ID? { self._elements.first?.id } - - /// The number of stored elements. - @inlinable - var count: Int { self._elements.count } - - /// Whether there are no stored elements. - @inlinable - var isEmpty: Bool { self._elements.isEmpty } - - /// Appends an element to the collection. - @inlinable - mutating func append(_ element: Element) { - self._elements.append(_IdentifiableElement(element: element, id: self.nextElementID())) - } - - /// Removes the first element from the collection. - @discardableResult - @inlinable - mutating func removeFirst() -> Element { - let removed = self._elements.removeFirst() - return removed.element - } - - @usableFromInline - enum ElementLookup { - /// The element was found in the collection. - case found(Element) - /// The element isn't in the collection, but it could be in the future. - case maybeAvailableLater - /// The element was in the collection, but is no longer available. - case noLongerAvailable - } - - /// Lookup the element with the given ID. - /// - /// - Parameter id: The ID of the element to lookup. - @inlinable - mutating func lookupElement(withID id: ID) -> ElementLookup { - guard let low = self.lowestID, let high = self.highestID else { - // Must be empty. - return id >= self._nextID ? .maybeAvailableLater : .noLongerAvailable - } - assert(low <= high) - - let lookup: ElementLookup - - if id < low { - lookup = .noLongerAvailable - } else if id > high { - lookup = .maybeAvailableLater - } else { - // IDs are monotonically increasing. If the buffer contains the tag we can use it to index - // into the deque by looking at the offsets. - let offset = low.distance(to: id) - let index = self._elements.startIndex.advanced(by: offset) - lookup = .found(self._elements[index].element) - } - - return lookup - } - } -} - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -extension _BroadcastSequenceStateMachine { - /// A collection of subscriptions. - @usableFromInline - struct Subscriptions: Sendable { - @usableFromInline - struct ID: Hashable, Sendable { - @usableFromInline - private(set) var rawValue: Int - - @inlinable - init() { - self.rawValue = 0 - } - - @inlinable - mutating func formNext() { - self.rawValue += 1 - } - - @inlinable - func next() -> Self { - var copy = self - copy.formNext() - return copy - } - } - - @usableFromInline - struct _Subscriber: Sendable { - /// The ID of the subscriber. - @usableFromInline - var id: ID - - /// The ID of the next element the subscriber will consume. - @usableFromInline - var nextElementID: _BroadcastSequenceStateMachine.Elements.ID - - /// A continuation which which will be resumed when the next element becomes available. - @usableFromInline - var continuation: ConsumerContinuation? - - @inlinable - init( - id: ID, - nextElementID: _BroadcastSequenceStateMachine.Elements.ID, - continuation: ConsumerContinuation? = nil - ) { - self.id = id - self.nextElementID = nextElementID - self.continuation = continuation - } - - /// Returns and sets the continuation to `nil` if one exists. - /// - /// The next element ID is advanced if a contination exists. - /// - /// - Returns: The continuation, if one existed. - @inlinable - mutating func takeContinuation() -> ConsumerContinuation? { - guard let continuation = self.continuation else { return nil } - self.continuation = nil - self.nextElementID.formNext() - return continuation - } - } - - @usableFromInline - var _subscribers: [_Subscriber] - @usableFromInline - var _nextSubscriberID: ID - - @inlinable - init() { - self._subscribers = [] - self._nextSubscriberID = ID() - } - - /// Returns the number of subscribers. - @inlinable - var count: Int { self._subscribers.count } - - /// Returns whether the collection is empty. - @inlinable - var isEmpty: Bool { self._subscribers.isEmpty } - - /// Adds a new subscriber and returns its unique ID. - /// - /// - Returns: The ID of the new subscriber. - @inlinable - mutating func subscribe() -> ID { - let id = self._nextSubscriberID - self._nextSubscriberID.formNext() - self._subscribers.append(_Subscriber(id: id, nextElementID: .initial)) - return id - } - - /// Provides mutable access to the element ID of the given subscriber, if it exists. - /// - /// - Parameters: - /// - id: The ID of the subscriber. - /// - body: A closure to mutate the element ID of the subscriber which returns the result and - /// a boolean indicating whether the subscriber should be removed. - /// - Returns: The result returned from the closure or `nil` if no subscriber exists with the - /// given ID. - @inlinable - mutating func withMutableElementID( - forSubscriber id: ID, - _ body: ( - inout _BroadcastSequenceStateMachine.Elements.ID - ) -> (result: R, removeSubscription: Bool) - ) -> R? { - guard let index = self._subscribers.firstIndex(where: { $0.id == id }) else { return nil } - let (result, removeSubscription) = body(&self._subscribers[index].nextElementID) - if removeSubscription { - self._subscribers.remove(at: index) - } - return result - } - - /// Sets the continuation for the subscription with the given ID. - /// - Parameters: - /// - continuation: The continuation to set. - /// - id: The ID of the subscriber. - /// - Returns: A boolean indicating whether the continuation was set or not. - @inlinable - mutating func setContinuation( - _ continuation: ConsumerContinuation, - forSubscriber id: ID - ) -> Bool { - guard let index = self._subscribers.firstIndex(where: { $0.id == id }) else { - return false - } - - assert(self._subscribers[index].continuation == nil) - self._subscribers[index].continuation = continuation - return true - } - - /// Returns an array of subscriber IDs which are whose next element ID is `id`. - @inlinable - func subscribers( - withElementID id: _BroadcastSequenceStateMachine.Elements.ID - ) -> [ID] { - return self._subscribers.filter { - $0.nextElementID == id - }.map { - $0.id - } - } - - /// Removes the subscriber with the given ID. - /// - Parameter id: The ID of the subscriber to remove. - /// - Returns: A tuple indicating whether a subscriber was removed and any continuation - /// associated with the subscriber. - @inlinable - mutating func removeSubscriber(withID id: ID) -> (Bool, ConsumerContinuation?) { - guard let index = self._subscribers.firstIndex(where: { $0.id == id }) else { - return (false, nil) - } - - let continuation = self._subscribers[index].continuation - self._subscribers.remove(at: index) - return (true, continuation) - } - - /// Remove all subscribers in the given array of IDs. - @inlinable - mutating func removeAllSubscribers(in idsToRemove: [ID]) { - self._subscribers.removeAll { - idsToRemove.contains($0.id) - } - } - - /// Remove all subscribers and return their IDs. - @inlinable - mutating func removeAllSubscribers() -> [ID] { - let subscribers = self._subscribers.map { $0.id } - self._subscribers.removeAll() - return subscribers - } - - /// Returns any continuations set on subscribers, unsetting at the same time. - @inlinable - mutating func takeContinuations() -> _OneOrMany? { - // Avoid allocs if there's only one subscriber. - let count = self._countPendingContinuations() - let result: _OneOrMany? - - switch count { - case 0: - result = nil - - case 1: - let index = self._subscribers.firstIndex(where: { $0.continuation != nil })! - let continuation = self._subscribers[index].takeContinuation()! - result = .one(continuation) - - default: - var continuations = [ConsumerContinuation]() - continuations.reserveCapacity(count) - - for index in self._subscribers.indices { - if let continuation = self._subscribers[index].takeContinuation() { - continuations.append(continuation) - } - } - - result = .many(continuations) - } - - return result - } - - /// Removes all subscribers which have continuations and return their continuations. - @inlinable - mutating func removeSubscribersWithContinuations() -> _OneOrMany { - // Avoid allocs if there's only one subscriber. - let count = self._countPendingContinuations() - let result: _OneOrMany - - switch count { - case 0: - result = .many([]) - - case 1: - let index = self._subscribers.firstIndex(where: { $0.continuation != nil })! - let subscription = self._subscribers.remove(at: index) - result = .one(subscription.continuation!) - - default: - var continuations = [ConsumerContinuation]() - continuations.reserveCapacity(count) - var removable = [ID]() - removable.reserveCapacity(count) - - for subscription in self._subscribers { - if let continuation = subscription.continuation { - continuations.append(continuation) - removable.append(subscription.id) - } - } - - self._subscribers.removeAll { - removable.contains($0.id) - } - - result = .many(continuations) - } - - return result - } - - @inlinable - func _countPendingContinuations() -> Int { - return self._subscribers.reduce(into: 0) { count, subscription in - if subscription.continuation != nil { - count += 1 - } - } - } - } -} - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -extension _BroadcastSequenceStateMachine { - // TODO: tiny array - @usableFromInline - enum _OneOrMany { - case one(Value) - case many([Value]) - } -} diff --git a/Sources/GRPCCore/Streaming/Internal/GRPCAsyncThrowingStream.swift b/Sources/GRPCCore/Streaming/Internal/GRPCAsyncThrowingStream.swift deleted file mode 100644 index 5b4bcb58b..000000000 --- a/Sources/GRPCCore/Streaming/Internal/GRPCAsyncThrowingStream.swift +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -// This exists to provide a version of 'AsyncThrowingStream' which is constrained to 'Sendable' -// elements. This is required in order for the continuation to be compatible with -// 'RPCWriterProtocol'. (Adding a constrained conformance to 'RPCWriterProtocol' on -// 'AsyncThrowingStream.Continuation' isn't possible because 'Sendable' is a marker protocol.) - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -package struct GRPCAsyncThrowingStream: AsyncSequence, Sendable { - package typealias Element = Element - package typealias Failure = any Error - - private let base: AsyncThrowingStream - - package static func makeStream( - of: Element.Type = Element.self - ) -> (stream: Self, continuation: Self.Continuation) { - let base = AsyncThrowingStream.makeStream(of: Element.self) - let stream = GRPCAsyncThrowingStream(base: base.stream) - let continuation = GRPCAsyncThrowingStream.Continuation(base: base.continuation) - return (stream, continuation) - } - - fileprivate init(base: AsyncThrowingStream) { - self.base = base - } - - package struct Continuation: Sendable { - private let base: AsyncThrowingStream.Continuation - - fileprivate init(base: AsyncThrowingStream.Continuation) { - self.base = base - } - - func yield(_ value: Element) { - self.base.yield(value) - } - - func finish(throwing error: (any Error)? = nil) { - self.base.finish(throwing: error) - } - } - - package func makeAsyncIterator() -> AsyncIterator { - AsyncIterator(base: self.base.makeAsyncIterator()) - } - - package struct AsyncIterator: AsyncIteratorProtocol { - private var base: AsyncThrowingStream.AsyncIterator - - fileprivate init(base: AsyncThrowingStream.AsyncIterator) { - self.base = base - } - - package mutating func next() async throws(any Error) -> Element? { - try await self.next(isolation: nil) - } - - package mutating func next( - isolation actor: isolated (any Actor)? - ) async throws(any Error) -> Element? { - try await self.base.next(isolation: `actor`) - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension GRPCAsyncThrowingStream.Continuation: RPCWriterProtocol { - package func write(_ element: Element) async throws { - self.yield(element) - } - - package func write(contentsOf elements: some Sequence) async throws { - for element in elements { - self.yield(element) - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension GRPCAsyncThrowingStream.Continuation: ClosableRPCWriterProtocol { - package func finish() async { - self.finish(throwing: nil) - } - - package func finish(throwing error: any Error) async { - self.finish(throwing: .some(error)) - } -} diff --git a/Sources/GRPCCore/Streaming/Internal/RPCWriter+Map.swift b/Sources/GRPCCore/Streaming/Internal/RPCWriter+Map.swift deleted file mode 100644 index 7755b46e1..000000000 --- a/Sources/GRPCCore/Streaming/Internal/RPCWriter+Map.swift +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -@usableFromInline -struct MapRPCWriter< - Value: Sendable, - Mapped: Sendable, - Base: RPCWriterProtocol ->: RPCWriterProtocol { - @usableFromInline - typealias Element = Value - - @usableFromInline - let base: Base - @usableFromInline - let transform: @Sendable (Value) throws -> Mapped - - @inlinable - init(base: Base, transform: @escaping @Sendable (Value) throws -> Mapped) { - self.base = base - self.transform = transform - } - - @inlinable - func write(_ element: Element) async throws { - try await self.base.write(self.transform(element)) - } - - @inlinable - func write(contentsOf elements: some Sequence) async throws { - let transformed = try elements.lazy.map { try self.transform($0) } - try await self.base.write(contentsOf: transformed) - } -} - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -extension RPCWriter { - @inlinable - static func map( - into writer: some RPCWriterProtocol, - transform: @Sendable @escaping (Element) throws -> Mapped - ) -> Self { - let mapper = MapRPCWriter(base: writer, transform: transform) - return RPCWriter(wrapping: mapper) - } -} diff --git a/Sources/GRPCCore/Streaming/Internal/RPCWriter+MessageToRPCResponsePart.swift b/Sources/GRPCCore/Streaming/Internal/RPCWriter+MessageToRPCResponsePart.swift deleted file mode 100644 index c3dc290e9..000000000 --- a/Sources/GRPCCore/Streaming/Internal/RPCWriter+MessageToRPCResponsePart.swift +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -@usableFromInline -struct MessageToRPCResponsePartWriter< - Serializer: MessageSerializer ->: RPCWriterProtocol where Serializer.Message: Sendable { - @usableFromInline - typealias Element = Serializer.Message - - @usableFromInline - let base: RPCWriter - @usableFromInline - let serializer: Serializer - - @inlinable - init(serializer: Serializer, base: some RPCWriterProtocol) { - self.serializer = serializer - self.base = RPCWriter(wrapping: base) - } - - @inlinable - func write(_ element: Element) async throws { - try await self.base.write(.message(self.serializer.serialize(element))) - } - - @inlinable - func write(contentsOf elements: some Sequence) async throws { - let requestParts = try elements.map { message -> RPCResponsePart in - .message(try self.serializer.serialize(message)) - } - - try await self.base.write(contentsOf: requestParts) - } -} - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -extension RPCWriter { - @inlinable - static func serializingToRPCResponsePart( - into writer: some RPCWriterProtocol, - with serializer: some MessageSerializer - ) -> Self { - return RPCWriter(wrapping: MessageToRPCResponsePartWriter(serializer: serializer, base: writer)) - } -} diff --git a/Sources/GRPCCore/Streaming/Internal/RPCWriter+Serialize.swift b/Sources/GRPCCore/Streaming/Internal/RPCWriter+Serialize.swift deleted file mode 100644 index f2a9acd22..000000000 --- a/Sources/GRPCCore/Streaming/Internal/RPCWriter+Serialize.swift +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -@usableFromInline -struct SerializingRPCWriter< - Base: RPCWriterProtocol<[UInt8]>, - Serializer: MessageSerializer ->: RPCWriterProtocol where Serializer.Message: Sendable { - @usableFromInline - typealias Element = Serializer.Message - - @usableFromInline - let base: Base - @usableFromInline - let serializer: Serializer - - @inlinable - init(serializer: Serializer, base: Base) { - self.serializer = serializer - self.base = base - } - - @inlinable - func write(_ element: Element) async throws { - try await self.base.write(self.serializer.serialize(element)) - } - - @inlinable - func write(contentsOf elements: some Sequence) async throws { - let requestParts = try elements.map { message in - try self.serializer.serialize(message) - } - - try await self.base.write(contentsOf: requestParts) - } -} - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -extension RPCWriter { - @inlinable - static func serializing( - into writer: some RPCWriterProtocol<[UInt8]>, - with serializer: some MessageSerializer - ) -> Self { - return RPCWriter(wrapping: SerializingRPCWriter(serializer: serializer, base: writer)) - } -} diff --git a/Sources/GRPCCore/Streaming/Internal/UncheckedAsyncIteratorSequence.swift b/Sources/GRPCCore/Streaming/Internal/UncheckedAsyncIteratorSequence.swift deleted file mode 100644 index e3bdcafee..000000000 --- a/Sources/GRPCCore/Streaming/Internal/UncheckedAsyncIteratorSequence.swift +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -public import Synchronization // should be @usableFromInline - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -@usableFromInline -/// An `AsyncSequence` which wraps an existing async iterator. -final class UncheckedAsyncIteratorSequence< - Base: AsyncIteratorProtocol ->: AsyncSequence, @unchecked Sendable { - // This is '@unchecked Sendable' because iterators are typically marked as not being Sendable - // to avoid multiple iterators being created. This is done to avoid multiple concurrent consumers - // of a single async sequence. - // - // However, gRPC needs to read the first message in a sequence of inbound request/response parts - // to check how the RPC should be handled. To do this it creates an async iterator and waits for - // the first value and then decides what to do. If it continues processing the RPC it uses this - // wrapper type to turn the iterator back into an async sequence and then drops the iterator on - // the floor so that there is only a single consumer of the original source. - - @usableFromInline - typealias Element = Base.Element - - /// The base iterator. - @usableFromInline - private(set) var base: Base - - /// Set to `true` when an iterator has been made. - @usableFromInline - let _hasMadeIterator = Atomic(false) - - @inlinable - init(_ base: Base) { - self.base = base - } - - @usableFromInline - struct AsyncIterator: AsyncIteratorProtocol { - @usableFromInline - private(set) var base: Base - - @inlinable - init(base: Base) { - self.base = base - } - - @inlinable - mutating func next() async throws -> Element? { - try await self.base.next() - } - } - - @inlinable - func makeAsyncIterator() -> AsyncIterator { - let (exchanged, original) = self._hasMadeIterator.compareExchange( - expected: false, - desired: true, - ordering: .relaxed - ) - - guard exchanged else { - fatalError("Only one iterator can be made") - } - - assert(!original) - return AsyncIterator(base: self.base) - } -} diff --git a/Sources/GRPCCore/Streaming/RPCAsyncSequence.swift b/Sources/GRPCCore/Streaming/RPCAsyncSequence.swift deleted file mode 100644 index d3603fe00..000000000 --- a/Sources/GRPCCore/Streaming/RPCAsyncSequence.swift +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -/// A type-erasing `AsyncSequence`. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -public struct RPCAsyncSequence< - Element: Sendable, - Failure: Error ->: AsyncSequence, @unchecked Sendable { - // @unchecked Sendable is required because 'any' doesn't support composition with primary - // associated types. (see: https://github.com/swiftlang/swift/issues/63877) - // - // To work around that limitation the 'init' requires that the async sequence being wrapped - // is 'Sendable' but that constraint must be dropped internally. This is safe, the compiler just - // can't prove it. - @usableFromInline - let _wrapped: any AsyncSequence - - /// Creates an ``RPCAsyncSequence`` by wrapping another `AsyncSequence`. - @inlinable - public init>( - wrapping other: Source - ) where Source: Sendable { - self._wrapped = other - } - - @inlinable - public func makeAsyncIterator() -> AsyncIterator { - AsyncIterator(wrapping: self._wrapped.makeAsyncIterator()) - } - - public struct AsyncIterator: AsyncIteratorProtocol { - @usableFromInline - private(set) var iterator: any AsyncIteratorProtocol - - @inlinable - init( - wrapping other: some AsyncIteratorProtocol - ) { - self.iterator = other - } - - @inlinable - public mutating func next( - isolation actor: isolated (any Actor)? - ) async throws(Failure) -> Element? { - try await self.iterator.next(isolation: `actor`) - } - - @inlinable - public mutating func next() async throws -> Element? { - try await self.next(isolation: nil) - } - } -} - -@available(*, unavailable) -extension RPCAsyncSequence.AsyncIterator: Sendable {} diff --git a/Sources/GRPCCore/Streaming/RPCWriter+Closable.swift b/Sources/GRPCCore/Streaming/RPCWriter+Closable.swift deleted file mode 100644 index dda689464..000000000 --- a/Sources/GRPCCore/Streaming/RPCWriter+Closable.swift +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -extension RPCWriter { - public struct Closable: ClosableRPCWriterProtocol { - @usableFromInline - let writer: any ClosableRPCWriterProtocol - - /// Creates an ``RPCWriter`` by wrapping the `other` writer. - /// - /// - Parameter other: The writer to wrap. - @inlinable - public init(wrapping other: some ClosableRPCWriterProtocol) { - self.writer = other - } - - /// Writes a single element. - /// - /// This function suspends until the element has been accepted. Implementers can use this - /// to exert backpressure on callers. - /// - /// - Parameter element: The element to write. - @inlinable - public func write(_ element: Element) async throws { - try await self.writer.write(element) - } - - /// Writes a sequence of elements. - /// - /// This function suspends until the elements have been accepted. Implementers can use this - /// to exert backpressure on callers. - /// - /// - Parameter elements: The elements to write. - @inlinable - public func write(contentsOf elements: some Sequence) async throws { - try await self.writer.write(contentsOf: elements) - } - - /// Indicate to the writer that no more writes are to be accepted. - /// - /// All writes after ``finish()`` has been called should result in an error - /// being thrown. - @inlinable - public func finish() async { - await self.writer.finish() - } - - /// Indicate to the writer that no more writes are to be accepted because an error occurred. - /// - /// All writes after ``finish(throwing:)`` has been called should result in an error - /// being thrown. - @inlinable - public func finish(throwing error: any Error) async { - await self.writer.finish(throwing: error) - } - } -} diff --git a/Sources/GRPCCore/Streaming/RPCWriter.swift b/Sources/GRPCCore/Streaming/RPCWriter.swift deleted file mode 100644 index d0b7b940b..000000000 --- a/Sources/GRPCCore/Streaming/RPCWriter.swift +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -/// A type-erasing ``RPCWriterProtocol``. -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -public struct RPCWriter: Sendable, RPCWriterProtocol { - private let writer: any RPCWriterProtocol - - /// Creates an ``RPCWriter`` by wrapping the `other` writer. - /// - /// - Parameter other: The writer to wrap. - public init(wrapping other: some RPCWriterProtocol) { - self.writer = other - } - - /// Writes a single element. - /// - /// This function suspends until the element has been accepted. Implementers can use this - /// to exert backpressure on callers. - /// - /// - Parameter element: The element to write. - public func write(_ element: Element) async throws { - try await self.writer.write(element) - } - - /// Writes a sequence of elements. - /// - /// This function suspends until the elements have been accepted. Implementers can use this - /// to exert backpressure on callers. - /// - /// - Parameter elements: The elements to write. - public func write(contentsOf elements: some Sequence) async throws { - try await self.writer.write(contentsOf: elements) - } -} diff --git a/Sources/GRPCCore/Streaming/RPCWriterProtocol.swift b/Sources/GRPCCore/Streaming/RPCWriterProtocol.swift deleted file mode 100644 index 63e3ee26d..000000000 --- a/Sources/GRPCCore/Streaming/RPCWriterProtocol.swift +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -/// A sink for values which are produced over time. -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -public protocol RPCWriterProtocol: Sendable { - /// The type of value written. - associatedtype Element: Sendable - - /// Writes a single element. - /// - /// This function suspends until the element has been accepted. Implementers can use this - /// to exert backpressure on callers. - /// - /// - Parameter element: The element to write. - func write(_ element: Element) async throws - - /// Writes a sequence of elements. - /// - /// This function suspends until the elements have been accepted. Implementers can use this - /// to exert backpressure on callers. - /// - /// - Parameter elements: The elements to write. - func write(contentsOf elements: some Sequence) async throws -} - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -extension RPCWriterProtocol { - /// Writes an `AsyncSequence` of values into the sink. - /// - /// - Parameter elements: The elements to write. - public func write( - contentsOf elements: Elements - ) async throws where Elements.Element == Element { - for try await element in elements { - try await self.write(element) - } - } -} - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -public protocol ClosableRPCWriterProtocol: RPCWriterProtocol { - /// Indicate to the writer that no more writes are to be accepted. - /// - /// All writes after ``finish()`` has been called should result in an error - /// being thrown. - func finish() async - - /// Indicate to the writer that no more writes are to be accepted because an error occurred. - /// - /// All writes after ``finish(throwing:)`` has been called should result in an error - /// being thrown. - func finish(throwing error: any Error) async -} diff --git a/Sources/GRPCCore/Timeout.swift b/Sources/GRPCCore/Timeout.swift deleted file mode 100644 index 4640a5cca..000000000 --- a/Sources/GRPCCore/Timeout.swift +++ /dev/null @@ -1,221 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -internal import Dispatch - -/// A timeout for a gRPC call. -/// -/// It's a combination of an amount (expressed as an integer of at maximum 8 digits), and a unit, which is -/// one of ``Timeout/Unit`` (hours, minutes, seconds, milliseconds, microseconds or nanoseconds). -/// -/// Timeouts must be positive and at most 8-digits long. -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -@usableFromInline -struct Timeout: CustomStringConvertible, Hashable, Sendable { - /// Possible units for a ``Timeout``. - internal enum Unit: Character { - case hours = "H" - case minutes = "M" - case seconds = "S" - case milliseconds = "m" - case microseconds = "u" - case nanoseconds = "n" - } - - /// The largest amount of any unit of time which may be represented by a gRPC timeout. - static let maxAmount: Int64 = 99_999_999 - - private let amount: Int64 - private let unit: Unit - - @usableFromInline - var duration: Duration { - Duration(amount: amount, unit: unit) - } - - /// The wire encoding of this timeout as described in the gRPC protocol. - /// See "Timeout" in https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests - var wireEncoding: String { - "\(amount)\(unit.rawValue)" - } - - @usableFromInline - var description: String { - return self.wireEncoding - } - - @usableFromInline - init?(decoding value: String) { - guard (2 ... 8).contains(value.count) else { - return nil - } - - if let amount = Int64(value.dropLast()), - let unit = Unit(rawValue: value.last!) - { - self = Self.init(amount: amount, unit: unit) - } else { - return nil - } - } - - /// Create a ``Timeout`` from a ``Duration``. - /// - /// - Important: It's not possible to know with what precision the duration was created: that is, - /// it's not possible to know whether `Duration.seconds(value)` or `Duration.milliseconds(value)` - /// was used. For this reason, the unit chosen for the ``Timeout`` (and thus the wire encoding) may be - /// different from the one originally used to create the `Duration`. Despite this, we guarantee that - /// both durations will be equivalent if there was no loss in precision during the transformation. - /// For example, `Duration.hours(123)` will yield a ``Timeout`` with `wireEncoding` equal to - /// `"442800S"`, which is in seconds. However, 442800 seconds and 123 hours are equivalent. - /// However, you must note that there may be some loss of precision when dealing with transforming - /// between units. For example, for very low precisions, such as a duration of only a few attoseconds, - /// given the smallest unit we have is whole nanoseconds, we cannot represent it. Same when converting - /// for instance, milliseconds to seconds. In these scenarios, we'll round to the closest whole number in - /// the target unit. - @usableFromInline - init(duration: Duration) { - let (seconds, attoseconds) = duration.components - - if seconds == 0 { - // There is no seconds component, so only pay attention to the attoseconds. - // Try converting to nanoseconds first, and continue rounding up if the - // max amount of digits is exceeded. - let nanoseconds = Int64(Double(attoseconds) / 1e+9) - self.init(rounding: nanoseconds, unit: .nanoseconds) - } else if Self.exceedsDigitLimit(seconds) { - // We don't have enough digits to represent this amount in seconds, so - // we will have to use minutes or hours. - // We can also ignore attoseconds, since we won't have enough precision - // anyways to represent the (at most) one second that the attoseconds - // component can express. - self.init(rounding: seconds, unit: .seconds) - } else { - // We can't convert seconds to nanoseconds because that would take us - // over the 8 digit limit (1 second = 1e+9 nanoseconds). - // We can however, try converting to microseconds or milliseconds. - let nanoseconds = Int64(Double(attoseconds) / 1e+9) - let microseconds = nanoseconds / 1000 - if microseconds == 0 { - self.init(amount: seconds, unit: .seconds) - } else { - let secondsInMicroseconds = seconds * 1000 * 1000 - let totalMicroseconds = microseconds + secondsInMicroseconds - self.init(rounding: totalMicroseconds, unit: .microseconds) - } - } - } - - /// Create a timeout by rounding up the timeout so that it may be represented in the gRPC - /// wire format. - private init(rounding amount: Int64, unit: Unit) { - var roundedAmount = amount - var roundedUnit = unit - - if roundedAmount <= 0 { - roundedAmount = 0 - } else { - while roundedAmount > Timeout.maxAmount { - switch roundedUnit { - case .nanoseconds: - roundedAmount = roundedAmount.quotientRoundedUp(dividingBy: 1000) - roundedUnit = .microseconds - case .microseconds: - roundedAmount = roundedAmount.quotientRoundedUp(dividingBy: 1000) - roundedUnit = .milliseconds - case .milliseconds: - roundedAmount = roundedAmount.quotientRoundedUp(dividingBy: 1000) - roundedUnit = .seconds - case .seconds: - roundedAmount = roundedAmount.quotientRoundedUp(dividingBy: 60) - roundedUnit = .minutes - case .minutes: - roundedAmount = roundedAmount.quotientRoundedUp(dividingBy: 60) - roundedUnit = .hours - case .hours: - roundedAmount = Timeout.maxAmount - roundedUnit = .hours - } - } - } - - self.init(amount: roundedAmount, unit: roundedUnit) - } - - private static func exceedsDigitLimit(_ value: Int64) -> Bool { - value > Timeout.maxAmount - } - - /// Creates a `GRPCTimeout`. - /// - /// - Precondition: The amount should be greater than or equal to zero and less than or equal - /// to `GRPCTimeout.maxAmount`. - internal init(amount: Int64, unit: Unit) { - precondition((0 ... Timeout.maxAmount).contains(amount)) - - self.amount = amount - self.unit = unit - } -} - -extension Int64 { - /// Returns the quotient of this value when divided by `divisor` rounded up to the nearest - /// multiple of `divisor` if the remainder is non-zero. - /// - /// - Parameter divisor: The value to divide this value by. - fileprivate func quotientRoundedUp(dividingBy divisor: Int64) -> Int64 { - let (quotient, remainder) = self.quotientAndRemainder(dividingBy: divisor) - return quotient + (remainder != 0 ? 1 : 0) - } -} - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -extension Duration { - /// Construct a `Duration` given a number of minutes represented as an `Int64`. - /// - /// let d: Duration = .minutes(5) - /// - /// - Returns: A `Duration` representing a given number of minutes. - internal static func minutes(_ minutes: Int64) -> Duration { - return Self.init(secondsComponent: 60 * minutes, attosecondsComponent: 0) - } - - /// Construct a `Duration` given a number of hours represented as an `Int64`. - /// - /// let d: Duration = .hours(3) - /// - /// - Returns: A `Duration` representing a given number of hours. - internal static func hours(_ hours: Int64) -> Duration { - return Self.init(secondsComponent: 60 * 60 * hours, attosecondsComponent: 0) - } - - internal init(amount: Int64, unit: Timeout.Unit) { - switch unit { - case .hours: - self = Self.hours(amount) - case .minutes: - self = Self.minutes(amount) - case .seconds: - self = Self.seconds(amount) - case .milliseconds: - self = Self.milliseconds(amount) - case .microseconds: - self = Self.microseconds(amount) - case .nanoseconds: - self = Self.nanoseconds(amount) - } - } -} diff --git a/Sources/GRPCCore/Transport/ClientTransport.swift b/Sources/GRPCCore/Transport/ClientTransport.swift deleted file mode 100644 index 800aa5b64..000000000 --- a/Sources/GRPCCore/Transport/ClientTransport.swift +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -public protocol ClientTransport: Sendable { - typealias Inbound = RPCAsyncSequence - typealias Outbound = RPCWriter.Closable - - /// Returns a throttle which gRPC uses to determine whether retries can be executed. - /// - /// Client transports don't need to implement the throttle or interact with it beyond its - /// creation. gRPC will record the results of requests to determine whether retries can be - /// performed. - var retryThrottle: RetryThrottle? { get } - - /// Establish and maintain a connection to the remote destination. - /// - /// Maintains a long-lived connection, or set of connections, to a remote destination. - /// Connections may be added or removed over time as required by the implementation and the - /// demand for streams by the client. - /// - /// Implementations of this function will typically create a long-lived task group which - /// maintains connections. The function exits when all open streams have been closed and new connections - /// are no longer required by the caller who signals this by calling ``beginGracefulShutdown()``, or by cancelling the - /// task this function runs in. - func connect() async throws - - /// Signal to the transport that no new streams may be created. - /// - /// Existing streams may run to completion naturally but calling - /// ``ClientTransport/withStream(descriptor:options:_:)`` should result in an ``RPCError`` with - /// code ``RPCError/Code/failedPrecondition`` being thrown. - /// - /// If you want to forcefully cancel all active streams then cancel the task - /// running ``connect()``. - func beginGracefulShutdown() - - /// Opens a stream using the transport, and uses it as input into a user-provided closure. - /// - /// - Important: The opened stream is closed after the closure is finished. - /// - /// Transport implementations should throw an ``RPCError`` with the following error codes: - /// - ``RPCError/Code/failedPrecondition`` if the transport is closing or has been closed. - /// - ``RPCError/Code/unavailable`` if it's temporarily not possible to create a stream and it - /// may be possible after some backoff period. - /// - /// - Parameters: - /// - descriptor: A description of the method to open a stream for. - /// - options: Options specific to the stream. - /// - closure: A closure that takes the opened stream as parameter. - /// - Returns: Whatever value was returned from `closure`. - func withStream( - descriptor: MethodDescriptor, - options: CallOptions, - _ closure: (_ stream: RPCStream) async throws -> T - ) async throws -> T - - /// Returns the configuration for a given method. - /// - /// - Parameter descriptor: The method to lookup configuration for. - /// - Returns: Configuration for the method, if it exists. - func config(forMethod descriptor: MethodDescriptor) -> MethodConfig? -} diff --git a/Sources/GRPCCore/Transport/RPCParts.swift b/Sources/GRPCCore/Transport/RPCParts.swift deleted file mode 100644 index cd72f7efb..000000000 --- a/Sources/GRPCCore/Transport/RPCParts.swift +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -/// Part of a request sent from a client to a server in a stream. -public enum RPCRequestPart: Hashable, Sendable { - /// Key-value pairs sent at the start of a request stream. Only one ``metadata(_:)`` value may - /// be sent to the server. - case metadata(Metadata) - - /// The bytes of a serialized message to send to the server. A stream may have any number of - /// messages sent on it. Restrictions for unary request or response streams are imposed at a - /// higher level. - case message([UInt8]) -} - -/// Part of a response sent from a server to a client in a stream. -public enum RPCResponsePart: Hashable, Sendable { - /// Key-value pairs sent at the start of the response stream. At most one ``metadata(_:)`` value - /// may be sent to the client. If the server sends ``metadata(_:)`` it must be the first part in - /// the response stream. - case metadata(Metadata) - - /// The bytes of a serialized message to send to the client. A stream may have any number of - /// messages sent on it. Restrictions for unary request or response streams are imposed at a - /// higher level. - case message([UInt8]) - - /// A status and key-value pairs sent to the client at the end of the response stream. Every - /// response stream must have exactly one ``status(_:_:)`` as the final part of the request - /// stream. - case status(Status, Metadata) -} diff --git a/Sources/GRPCCore/Transport/RPCStream.swift b/Sources/GRPCCore/Transport/RPCStream.swift deleted file mode 100644 index eca345c2a..000000000 --- a/Sources/GRPCCore/Transport/RPCStream.swift +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -/// A bidirectional communication channel between a client and server for a given method. -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -public struct RPCStream< - Inbound: AsyncSequence & Sendable, - Outbound: ClosableRPCWriterProtocol & Sendable ->: Sendable { - /// Information about the method this stream is for. - public var descriptor: MethodDescriptor - - /// A sequence of messages received from the network. - public var inbound: Inbound - - /// A writer for messages sent across the network. - public var outbound: Outbound - - public init(descriptor: MethodDescriptor, inbound: Inbound, outbound: Outbound) { - self.descriptor = descriptor - self.inbound = inbound - self.outbound = outbound - } -} diff --git a/Sources/GRPCCore/Transport/RetryThrottle.swift b/Sources/GRPCCore/Transport/RetryThrottle.swift deleted file mode 100644 index 70c934dfc..000000000 --- a/Sources/GRPCCore/Transport/RetryThrottle.swift +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -private import Synchronization - -/// A throttle used to rate-limit retries and hedging attempts. -/// -/// gRPC prevents servers from being overloaded by retries and hedging by using a token-based -/// throttling mechanism at the transport level. -/// -/// Each client transport maintains a throttle for the server it is connected to and gRPC records -/// successful and failed RPC attempts. Successful attempts increment the number of tokens -/// by ``tokenRatio`` and failed attempts decrement the available tokens by one. In the context -/// of throttling, a failed attempt is one where the server terminates the RPC with a status code -/// which is retryable or non fatal (as defined by ``RetryPolicy/retryableStatusCodes`` and -/// ``HedgingPolicy/nonFatalStatusCodes``) or when the client receives a pushback response from -/// the server. -/// -/// See also [gRFC A6: client retries](https://github.com/grpc/proposal/blob/master/A6-client-retries.md). -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -public final class RetryThrottle: Sendable { - // Note: only three figures after the decimal point from the original token ratio are used so - // all computation is done a scaled number of tokens (tokens * 1000). This allows us to do all - // computation in integer space. - - /// The number of tokens available, multiplied by 1000. - private let scaledTokensAvailable: Mutex - /// The number of tokens, multiplied by 1000. - private let scaledTokenRatio: Int - /// The maximum number of tokens, multiplied by 1000. - private let scaledMaxTokens: Int - /// The retry threshold, multiplied by 1000. If ``scaledTokensAvailable`` is above this then - /// retries are permitted. - private let scaledRetryThreshold: Int - - /// Returns the throttling token ratio. - /// - /// The number of tokens held by the throttle is incremented by this value for each successful - /// response. In the context of throttling, a successful response is one which: - /// - receives metadata from the server, or - /// - is terminated with a non-retryable or fatal status code. - /// - /// If the response is a pushback response then it is not considered to be successful, even if - /// either of the preceding conditions are met. - public var tokenRatio: Double { - Double(self.scaledTokenRatio) / 1000 - } - - /// The maximum number of tokens the throttle may hold. - public var maxTokens: Int { - self.scaledMaxTokens / 1000 - } - - /// The number of tokens the throttle currently has. - /// - /// If this value is less than or equal to the retry threshold (defined as `maxTokens / 2`) - /// then RPCs will not be retried and hedging will be disabled. - public var tokens: Double { - self.scaledTokensAvailable.withLock { - Double($0) / 1000 - } - } - - /// Returns whether retries and hedging are permitted at this time. - public var isRetryPermitted: Bool { - self.scaledTokensAvailable.withLock { - $0 > self.scaledRetryThreshold - } - } - - /// Create a new throttle. - /// - /// - Parameters: - /// - maxTokens: The maximum number of tokens available. Must be in the range `1...1000`. - /// - tokenRatio: The number of tokens to increment the available tokens by for successful - /// responses. See the documentation on this type for a description of what counts as a - /// successful response. Note that only three decimal places are used from this value. - /// - Precondition: `maxTokens` must be in the range `1...1000`. - /// - Precondition: `tokenRatio` must be `>= 0.001`. - public init(maxTokens: Int, tokenRatio: Double) { - precondition( - (1 ... 1000).contains(maxTokens), - "maxTokens must be in the range 1...1000 (is \(maxTokens))" - ) - - let scaledTokenRatio = Int(tokenRatio * 1000) - precondition(scaledTokenRatio > 0, "tokenRatio must be >= 0.001 (is \(tokenRatio))") - - let scaledTokens = maxTokens * 1000 - self.scaledMaxTokens = scaledTokens - self.scaledRetryThreshold = scaledTokens / 2 - self.scaledTokenRatio = scaledTokenRatio - self.scaledTokensAvailable = Mutex(scaledTokens) - } - - /// Create a new throttle. - /// - /// - Parameter policy: The policy to use to configure the throttle. - public convenience init(policy: ServiceConfig.RetryThrottling) { - self.init(maxTokens: policy.maxTokens, tokenRatio: policy.tokenRatio) - } - - /// Records a success, adding a token to the throttle. - @usableFromInline - func recordSuccess() { - self.scaledTokensAvailable.withLock { value in - value = min(self.scaledMaxTokens, value &+ self.scaledTokenRatio) - } - } - - /// Records a failure, removing tokens from the throttle. - /// - Returns: Whether retries will now be throttled. - @usableFromInline - @discardableResult - func recordFailure() -> Bool { - self.scaledTokensAvailable.withLock { value in - value = max(0, value &- 1000) - return value <= self.scaledRetryThreshold - } - } -} diff --git a/Sources/GRPCCore/Transport/ServerTransport.swift b/Sources/GRPCCore/Transport/ServerTransport.swift deleted file mode 100644 index 79c5c0fc6..000000000 --- a/Sources/GRPCCore/Transport/ServerTransport.swift +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -/// A protocol server transport implementations must conform to. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -public protocol ServerTransport: Sendable { - typealias Inbound = RPCAsyncSequence - typealias Outbound = RPCWriter.Closable - - /// Starts the transport. - /// - /// Implementations will typically bind to a listening port when this function is called - /// and start accepting new connections. Each accepted inbound RPC stream will be handed over to - /// the provided `streamHandler` to handle accordingly. - /// - /// You can call ``beginGracefulShutdown()`` to stop the transport from accepting new streams. Existing - /// streams must be allowed to complete naturally. However, transports may also enforce a grace - /// period after which any open streams may be cancelled. You can also cancel the task running - /// ``listen(streamHandler:)`` to abruptly close connections and streams. - func listen( - streamHandler: @escaping @Sendable ( - _ stream: RPCStream, - _ context: ServerContext - ) async -> Void - ) async throws - - /// Indicates to the transport that no new streams should be accepted. - /// - /// Existing streams are permitted to run to completion. However, the transport may also enforce - /// a grace period, after which remaining streams are cancelled. - func beginGracefulShutdown() -} diff --git a/Sources/GRPCHTTP2Core/Client/Connection/ClientConnectionHandler.swift b/Sources/GRPCHTTP2Core/Client/Connection/ClientConnectionHandler.swift deleted file mode 100644 index 4cf354857..000000000 --- a/Sources/GRPCHTTP2Core/Client/Connection/ClientConnectionHandler.swift +++ /dev/null @@ -1,681 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -package import NIOCore -package import NIOHTTP2 - -/// An event which happens on a client's HTTP/2 connection. -package enum ClientConnectionEvent: Sendable { - package enum CloseReason: Sendable { - /// The server sent a GOAWAY frame to the client. - case goAway(HTTP2ErrorCode, String) - /// The keep alive timer fired and subsequently timed out. - case keepaliveExpired - /// The connection became idle. - case idle - /// The local peer initiated the close. - case initiatedLocally - /// The connection was closed unexpectedly - case unexpected((any Error)?, isIdle: Bool) - } - - /// The connection is now ready. - case ready - - /// The connection has started shutting down, no new streams should be created. - case closing(CloseReason) -} - -/// A `ChannelHandler` which manages part of the lifecycle of a gRPC connection over HTTP/2. -/// -/// This handler is responsible for managing several aspects of the connection. These include: -/// 1. Periodically sending keep alive pings to the server (if configured) and closing the -/// connection if necessary. -/// 2. Closing the connection if it is idle (has no open streams) for a configured amount of time. -/// 3. Forwarding lifecycle events to the next handler. -/// -/// Some of the behaviours are described in [gRFC A8](https://github.com/grpc/proposal/blob/master/A8-client-side-keepalive.md). -package final class ClientConnectionHandler: ChannelInboundHandler, ChannelOutboundHandler { - package typealias InboundIn = HTTP2Frame - package typealias InboundOut = ClientConnectionEvent - - package typealias OutboundIn = Never - package typealias OutboundOut = HTTP2Frame - - package enum OutboundEvent: Hashable, Sendable { - /// Close the connection gracefully - case closeGracefully - } - - /// The `EventLoop` of the `Channel` this handler exists in. - private let eventLoop: any EventLoop - - /// The maximum amount of time the connection may be idle for. If the connection remains idle - /// (i.e. has no open streams) for this period of time then the connection will be gracefully - /// closed. - private var maxIdleTimer: Timer? - - /// The amount of time to wait before sending a keep alive ping. - private var keepaliveTimer: Timer? - - /// The amount of time the client has to reply after sending a keep alive ping. Only used if - /// `keepaliveTimer` is set. - private var keepaliveTimeoutTimer: Timer - - /// Opaque data sent in keep alive pings. - private let keepalivePingData: HTTP2PingData - - /// The current state of the connection. - private var state: StateMachine - - /// Whether a flush is pending. - private var flushPending: Bool - /// Whether `channelRead` has been called and `channelReadComplete` hasn't yet been called. - /// Resets once `channelReadComplete` returns. - private var inReadLoop: Bool - - /// The context of the channel this handler is in. - private var context: ChannelHandlerContext? - - /// Creates a new handler which manages the lifecycle of a connection. - /// - /// - Parameters: - /// - eventLoop: The `EventLoop` of the `Channel` this handler is placed in. - /// - maxIdleTime: The maximum amount time a connection may be idle for before being closed. - /// - keepaliveTime: The amount of time to wait after reading data before sending a keep-alive - /// ping. - /// - keepaliveTimeout: The amount of time the client has to reply after the server sends a - /// keep-alive ping to keep the connection open. The connection is closed if no reply - /// is received. - /// - keepaliveWithoutCalls: Whether the client sends keep-alive pings when there are no calls - /// in progress. - package init( - eventLoop: any EventLoop, - maxIdleTime: TimeAmount?, - keepaliveTime: TimeAmount?, - keepaliveTimeout: TimeAmount?, - keepaliveWithoutCalls: Bool - ) { - self.eventLoop = eventLoop - self.maxIdleTimer = maxIdleTime.map { Timer(delay: $0) } - self.keepaliveTimer = keepaliveTime.map { Timer(delay: $0, repeat: true) } - self.keepaliveTimeoutTimer = Timer(delay: keepaliveTimeout ?? .seconds(20)) - self.keepalivePingData = HTTP2PingData(withInteger: .random(in: .min ... .max)) - self.state = StateMachine(allowKeepaliveWithoutCalls: keepaliveWithoutCalls) - - self.flushPending = false - self.inReadLoop = false - } - - package func handlerAdded(context: ChannelHandlerContext) { - assert(context.eventLoop === self.eventLoop) - self.context = context - } - - package func handlerRemoved(context: ChannelHandlerContext) { - self.context = nil - } - - package func channelInactive(context: ChannelHandlerContext) { - switch self.state.closed() { - case .none: - () - - case .unexpectedClose(let error, let isIdle): - let event = self.wrapInboundOut(.closing(.unexpected(error, isIdle: isIdle))) - context.fireChannelRead(event) - - case .succeed(let promise): - promise.succeed() - } - - self.keepaliveTimer?.cancel() - self.keepaliveTimeoutTimer.cancel() - context.fireChannelInactive() - } - - package func userInboundEventTriggered(context: ChannelHandlerContext, event: Any) { - switch event { - case let event as NIOHTTP2StreamCreatedEvent: - self._streamCreated(event.streamID, channel: context.channel) - - case let event as StreamClosedEvent: - self._streamClosed(event.streamID, channel: context.channel) - - default: - () - } - - context.fireUserInboundEventTriggered(event) - } - - package func errorCaught(context: ChannelHandlerContext, error: any Error) { - // Store the error and close, this will result in the final close event being fired down - // the pipeline with an appropriate close reason and appropriate error. (This avoids - // the async channel just throwing the error.) - self.state.receivedError(error) - context.close(mode: .all, promise: nil) - } - - package func channelRead(context: ChannelHandlerContext, data: NIOAny) { - let frame = self.unwrapInboundIn(data) - self.inReadLoop = true - - switch frame.payload { - case .goAway(_, let errorCode, let data): - if errorCode == .noError { - // Receiving a GOAWAY frame means we need to stop creating streams immediately and start - // closing the connection. - switch self.state.beginGracefulShutdown(promise: nil) { - case .sendGoAway(let close): - // gRPC servers may indicate why the GOAWAY was sent in the opaque data. - let message = data.map { String(buffer: $0) } ?? "" - context.fireChannelRead(self.wrapInboundOut(.closing(.goAway(errorCode, message)))) - - // Clients should send GOAWAYs when closing a connection. - self.writeAndFlushGoAway(context: context, errorCode: .noError) - if close { - context.close(promise: nil) - } - - case .none: - () - } - } else { - // Some error, begin closing. - if self.state.beginClosing() { - // gRPC servers may indicate why the GOAWAY was sent in the opaque data. - let message = data.map { String(buffer: $0) } ?? "" - context.fireChannelRead(self.wrapInboundOut(.closing(.goAway(errorCode, message)))) - context.close(promise: nil) - } - } - - case .ping(let data, let ack): - // Pings are ack'd by the HTTP/2 handler so we only pay attention to acks here, and in - // particular only those carrying the keep-alive data. - if ack, data == self.keepalivePingData { - let loopBound = LoopBoundView(handler: self, context: context) - self.keepaliveTimeoutTimer.cancel() - self.keepaliveTimer?.schedule(on: context.eventLoop) { - loopBound.keepaliveTimerFired() - } - } - - case .settings(.settings(_)): - let isInitialSettings = self.state.receivedSettings() - - // The first settings frame indicates that the connection is now ready to use. The channel - // becoming active is insufficient as, for example, a TLS handshake may fail after - // establishing the TCP connection, or the server isn't configured for gRPC (or HTTP/2). - if isInitialSettings { - let loopBound = LoopBoundView(handler: self, context: context) - self.keepaliveTimer?.schedule(on: context.eventLoop) { - loopBound.keepaliveTimerFired() - } - - self.maxIdleTimer?.schedule(on: context.eventLoop) { - loopBound.maxIdleTimerFired() - } - - context.fireChannelRead(self.wrapInboundOut(.ready)) - } - - default: - () - } - } - - package func channelReadComplete(context: ChannelHandlerContext) { - while self.flushPending { - self.flushPending = false - context.flush() - } - - self.inReadLoop = false - context.fireChannelReadComplete() - } - - package func triggerUserOutboundEvent( - context: ChannelHandlerContext, - event: Any, - promise: EventLoopPromise? - ) { - if let event = event as? OutboundEvent { - switch event { - case .closeGracefully: - switch self.state.beginGracefulShutdown(promise: promise) { - case .sendGoAway(let close): - context.fireChannelRead(self.wrapInboundOut(.closing(.initiatedLocally))) - // The client could send a GOAWAY at this point but it's not really necessary, the server - // can't open streams anyway, the client will just close the connection when it's done. - if close { - context.close(promise: nil) - } - - case .none: - () - } - } - } else { - context.triggerUserOutboundEvent(event, promise: promise) - } - } -} - -extension ClientConnectionHandler { - struct LoopBoundView: @unchecked Sendable { - private let handler: ClientConnectionHandler - private let context: ChannelHandlerContext - - init(handler: ClientConnectionHandler, context: ChannelHandlerContext) { - self.handler = handler - self.context = context - } - - func keepaliveTimerFired() { - self.context.eventLoop.assertInEventLoop() - self.handler.keepaliveTimerFired(context: self.context) - } - - func keepaliveTimeoutExpired() { - self.context.eventLoop.assertInEventLoop() - self.handler.keepaliveTimeoutExpired(context: self.context) - } - - func maxIdleTimerFired() { - self.context.eventLoop.assertInEventLoop() - self.handler.maxIdleTimerFired(context: self.context) - } - } -} - -extension ClientConnectionHandler { - package struct HTTP2StreamDelegate: @unchecked Sendable, NIOHTTP2StreamDelegate { - // @unchecked is okay: the only methods do the appropriate event-loop dance. - - private let handler: ClientConnectionHandler - - init(_ handler: ClientConnectionHandler) { - self.handler = handler - } - - package func streamCreated(_ id: HTTP2StreamID, channel: any Channel) { - if self.handler.eventLoop.inEventLoop { - self.handler._streamCreated(id, channel: channel) - } else { - self.handler.eventLoop.execute { - self.handler._streamCreated(id, channel: channel) - } - } - } - - package func streamClosed(_ id: HTTP2StreamID, channel: any Channel) { - if self.handler.eventLoop.inEventLoop { - self.handler._streamClosed(id, channel: channel) - } else { - self.handler.eventLoop.execute { - self.handler._streamClosed(id, channel: channel) - } - } - } - } - - package var http2StreamDelegate: HTTP2StreamDelegate { - return HTTP2StreamDelegate(self) - } - - private func _streamCreated(_ id: HTTP2StreamID, channel: any Channel) { - self.eventLoop.assertInEventLoop() - - // Stream created, so the connection isn't idle. - self.maxIdleTimer?.cancel() - self.state.streamOpened(id) - } - - private func _streamClosed(_ id: HTTP2StreamID, channel: any Channel) { - guard let context = self.context else { return } - self.eventLoop.assertInEventLoop() - - switch self.state.streamClosed(id) { - case .startIdleTimer(let cancelKeepalive): - // All streams are closed, restart the idle timer, and stop the keep-alive timer (it may - // not stop if keep-alive is allowed when there are no active calls). - let loopBound = LoopBoundView(handler: self, context: context) - self.maxIdleTimer?.schedule(on: context.eventLoop) { - loopBound.maxIdleTimerFired() - } - - if cancelKeepalive { - self.keepaliveTimer?.cancel() - } - - case .close: - // Connection was closing but waiting for all streams to close. They must all be closed - // now so close the connection. - context.close(promise: nil) - - case .none: - () - } - } -} - -extension ClientConnectionHandler { - private func maybeFlush(context: ChannelHandlerContext) { - if self.inReadLoop { - self.flushPending = true - } else { - context.flush() - } - } - - private func keepaliveTimerFired(context: ChannelHandlerContext) { - guard self.state.sendKeepalivePing() else { return } - - // Cancel the keep alive timer when the client sends a ping. The timer is resumed when the ping - // is acknowledged. - self.keepaliveTimer?.cancel() - - let ping = HTTP2Frame(streamID: .rootStream, payload: .ping(self.keepalivePingData, ack: false)) - context.write(self.wrapOutboundOut(ping), promise: nil) - self.maybeFlush(context: context) - - // Schedule a timeout on waiting for the response. - let loopBound = LoopBoundView(handler: self, context: context) - self.keepaliveTimeoutTimer.schedule(on: context.eventLoop) { - loopBound.keepaliveTimeoutExpired() - } - } - - private func keepaliveTimeoutExpired(context: ChannelHandlerContext) { - guard self.state.beginClosing() else { return } - - context.fireChannelRead(self.wrapInboundOut(.closing(.keepaliveExpired))) - self.writeAndFlushGoAway(context: context, message: "keepalive_expired") - context.close(promise: nil) - } - - private func maxIdleTimerFired(context: ChannelHandlerContext) { - guard self.state.beginClosing() else { return } - - context.fireChannelRead(self.wrapInboundOut(.closing(.idle))) - self.writeAndFlushGoAway(context: context, message: "idle") - context.close(promise: nil) - } - - private func writeAndFlushGoAway( - context: ChannelHandlerContext, - errorCode: HTTP2ErrorCode = .noError, - message: String? = nil - ) { - let goAway = HTTP2Frame( - streamID: .rootStream, - payload: .goAway( - lastStreamID: 0, - errorCode: errorCode, - opaqueData: message.map { context.channel.allocator.buffer(string: $0) } - ) - ) - - context.write(self.wrapOutboundOut(goAway), promise: nil) - self.maybeFlush(context: context) - } -} - -extension ClientConnectionHandler { - struct StateMachine { - private var state: State - - private enum State { - case active(Active) - case closing(Closing) - case closed - case _modifying - - struct Active { - var openStreams: Set - var allowKeepaliveWithoutCalls: Bool - var receivedConnectionPreface: Bool - var error: (any Error)? - - init(allowKeepaliveWithoutCalls: Bool) { - self.openStreams = [] - self.allowKeepaliveWithoutCalls = allowKeepaliveWithoutCalls - self.receivedConnectionPreface = false - self.error = nil - } - - mutating func receivedSettings() -> Bool { - let isFirstSettingsFrame = !self.receivedConnectionPreface - self.receivedConnectionPreface = true - return isFirstSettingsFrame - } - } - - struct Closing { - var allowKeepaliveWithoutCalls: Bool - var openStreams: Set - var closePromise: Optional> - var isGraceful: Bool - - init(from state: Active, isGraceful: Bool, closePromise: EventLoopPromise?) { - self.openStreams = state.openStreams - self.isGraceful = isGraceful - self.allowKeepaliveWithoutCalls = state.allowKeepaliveWithoutCalls - self.closePromise = closePromise - } - } - } - - init(allowKeepaliveWithoutCalls: Bool) { - self.state = .active(State.Active(allowKeepaliveWithoutCalls: allowKeepaliveWithoutCalls)) - } - - /// Record that a SETTINGS frame was received from the remote peer. - /// - /// - Returns: `true` if this was the first SETTINGS frame received. - mutating func receivedSettings() -> Bool { - switch self.state { - case .active(var active): - self.state = ._modifying - let isFirstSettingsFrame = active.receivedSettings() - self.state = .active(active) - return isFirstSettingsFrame - - case .closing, .closed: - return false - - case ._modifying: - preconditionFailure() - } - } - - /// Record that an error was received. - mutating func receivedError(_ error: any Error) { - switch self.state { - case .active(var active): - self.state = ._modifying - active.error = error - self.state = .active(active) - case .closing, .closed: - () - case ._modifying: - preconditionFailure() - } - } - - /// Record that the stream with the given ID has been opened. - mutating func streamOpened(_ id: HTTP2StreamID) { - switch self.state { - case .active(var state): - self.state = ._modifying - let (inserted, _) = state.openStreams.insert(id) - assert(inserted, "Can't open stream \(Int(id)), it's already open") - self.state = .active(state) - - case .closing(var state): - self.state = ._modifying - let (inserted, _) = state.openStreams.insert(id) - assert(inserted, "Can't open stream \(Int(id)), it's already open") - self.state = .closing(state) - - case .closed: - () - - case ._modifying: - preconditionFailure() - } - } - - enum OnStreamClosed: Equatable { - /// Start the idle timer, after which the connection should be closed gracefully. - case startIdleTimer(cancelKeepalive: Bool) - /// Close the connection. - case close - /// Do nothing. - case none - } - - /// Record that the stream with the given ID has been closed. - mutating func streamClosed(_ id: HTTP2StreamID) -> OnStreamClosed { - let onStreamClosed: OnStreamClosed - - switch self.state { - case .active(var state): - self.state = ._modifying - let removedID = state.openStreams.remove(id) - assert(removedID != nil, "Can't close stream \(Int(id)), it wasn't open") - if state.openStreams.isEmpty { - onStreamClosed = .startIdleTimer(cancelKeepalive: !state.allowKeepaliveWithoutCalls) - } else { - onStreamClosed = .none - } - self.state = .active(state) - - case .closing(var state): - self.state = ._modifying - let removedID = state.openStreams.remove(id) - assert(removedID != nil, "Can't close stream \(Int(id)), it wasn't open") - onStreamClosed = state.openStreams.isEmpty ? .close : .none - self.state = .closing(state) - - case .closed: - onStreamClosed = .none - - case ._modifying: - preconditionFailure() - } - - return onStreamClosed - } - - /// Returns whether a keep alive ping should be sent to the server. - func sendKeepalivePing() -> Bool { - let sendKeepalivePing: Bool - - // Only send a ping if there are open streams or there are no open streams and keep alive - // is permitted when there are no active calls. - switch self.state { - case .active(let state): - sendKeepalivePing = !state.openStreams.isEmpty || state.allowKeepaliveWithoutCalls - case .closing(let state): - sendKeepalivePing = !state.openStreams.isEmpty || state.allowKeepaliveWithoutCalls - case .closed: - sendKeepalivePing = false - case ._modifying: - preconditionFailure() - } - - return sendKeepalivePing - } - - enum OnGracefulShutDown: Equatable { - case sendGoAway(Bool) - case none - } - - mutating func beginGracefulShutdown(promise: EventLoopPromise?) -> OnGracefulShutDown { - let onGracefulShutdown: OnGracefulShutDown - - switch self.state { - case .active(let state): - self.state = ._modifying - // Only close immediately if there are no open streams. The client doesn't need to - // ratchet down the last stream ID as only the client creates streams in gRPC. - let close = state.openStreams.isEmpty - onGracefulShutdown = .sendGoAway(close) - self.state = .closing(State.Closing(from: state, isGraceful: true, closePromise: promise)) - - case .closing(var state): - self.state = ._modifying - state.closePromise.setOrCascade(to: promise) - self.state = .closing(state) - onGracefulShutdown = .none - - case .closed: - onGracefulShutdown = .none - - case ._modifying: - preconditionFailure() - } - - return onGracefulShutdown - } - - /// Returns whether the connection should be closed. - mutating func beginClosing() -> Bool { - switch self.state { - case .active(let active): - self.state = .closing(State.Closing(from: active, isGraceful: false, closePromise: nil)) - return true - case .closing(var state): - self.state = ._modifying - let forceShutdown = state.isGraceful - state.isGraceful = false - self.state = .closing(state) - return forceShutdown - case .closed: - return false - case ._modifying: - preconditionFailure() - } - } - - enum OnClosed { - case succeed(EventLoopPromise) - case unexpectedClose((any Error)?, isIdle: Bool) - case none - } - - /// Marks the state as closed. - mutating func closed() -> OnClosed { - switch self.state { - case .active(let state): - self.state = .closed - return .unexpectedClose(state.error, isIdle: state.openStreams.isEmpty) - case .closing(let closing): - self.state = .closed - return closing.closePromise.map { .succeed($0) } ?? .none - case .closed: - self.state = .closed - return .none - case ._modifying: - preconditionFailure() - } - } - } -} diff --git a/Sources/GRPCHTTP2Core/Client/Connection/Connection.swift b/Sources/GRPCHTTP2Core/Client/Connection/Connection.swift deleted file mode 100644 index a90d2a9e8..000000000 --- a/Sources/GRPCHTTP2Core/Client/Connection/Connection.swift +++ /dev/null @@ -1,498 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -package import GRPCCore -package import NIOCore -package import NIOHTTP2 -private import Synchronization - -/// A `Connection` provides communication to a single remote peer. -/// -/// Each `Connection` object is 'one-shot': it may only be used for a single connection over -/// its lifetime. If a connect attempt fails then the `Connection` must be discarded and a new one -/// must be created. However, an active connection may be used multiple times to provide streams -/// to the backend. -/// -/// To use the `Connection` you must run it in a task. You can consume event updates by listening -/// to `events`: -/// -/// ```swift -/// await withTaskGroup(of: Void.self) { group in -/// group.addTask { await connection.run() } -/// -/// for await event in connection.events { -/// switch event { -/// case .connectSucceeded: -/// // ... -/// default: -/// // ... -/// } -/// } -/// } -/// ``` -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -package final class Connection: Sendable { - /// Events which can happen over the lifetime of the connection. - package enum Event: Sendable { - /// The connect attempt succeeded and the connection is ready to use. - case connectSucceeded - /// The connect attempt failed. - case connectFailed(any Error) - /// The connection received a GOAWAY and will close soon. No new streams - /// should be opened on this connection. - case goingAway(HTTP2ErrorCode, String) - /// The connection is closed. - case closed(Connection.CloseReason) - } - - /// The reason the connection closed. - package enum CloseReason: Sendable { - /// Closed because an idle timeout fired. - case idleTimeout - /// Closed because a keepalive timer fired. - case keepaliveTimeout - /// Closed because the caller initiated shutdown and all RPCs on the connection finished. - case initiatedLocally - /// Closed because the remote peer initiate shutdown (i.e. sent a GOAWAY frame). - case remote - /// Closed because the connection encountered an unexpected error. - case error(any Error, wasIdle: Bool) - } - - /// Inputs to the 'run' method. - private enum Input: Sendable { - case close - } - - /// Events which have happened to the connection. - private let event: (stream: AsyncStream, continuation: AsyncStream.Continuation) - - /// Events which the connection must react to. - private let input: (stream: AsyncStream, continuation: AsyncStream.Continuation) - - /// The address to connect to. - private let address: SocketAddress - - /// The default compression algorithm used for requests. - private let defaultCompression: CompressionAlgorithm - - /// The set of enabled compression algorithms. - private let enabledCompression: CompressionAlgorithmSet - - /// A connector used to establish a connection. - private let http2Connector: any HTTP2Connector - - /// The state of the connection. - private let state: Mutex - - /// The default max request message size in bytes, 4 MiB. - private static var defaultMaxRequestMessageSizeBytes: Int { - 4 * 1024 * 1024 - } - - /// A stream of events which can happen to the connection. - package var events: AsyncStream { - self.event.stream - } - - package init( - address: SocketAddress, - http2Connector: any HTTP2Connector, - defaultCompression: CompressionAlgorithm, - enabledCompression: CompressionAlgorithmSet - ) { - self.address = address - self.defaultCompression = defaultCompression - self.enabledCompression = enabledCompression - self.http2Connector = http2Connector - self.event = AsyncStream.makeStream(of: Event.self) - self.input = AsyncStream.makeStream(of: Input.self) - self.state = Mutex(.notConnected) - } - - /// Connect and run the connection. - /// - /// This function returns when the connection has closed. You can observe connection events - /// by consuming the ``events`` sequence. - package func run() async { - let connectResult = await Result { - try await self.http2Connector.establishConnection(to: self.address) - } - - switch connectResult { - case .success(let connected): - // Connected successfully, update state and report the event. - self.state.withLock { state in - state.connected(connected) - } - - await withDiscardingTaskGroup { group in - // Add a task to run the connection and consume events. - group.addTask { - try? await connected.channel.executeThenClose { inbound, outbound in - await self.consumeConnectionEvents(inbound) - } - } - - // Meanwhile, consume input events. This sequence will end when the connection has closed. - for await input in self.input.stream { - switch input { - case .close: - let asyncChannel = self.state.withLock { $0.beginClosing() } - if let channel = asyncChannel?.channel { - let event = ClientConnectionHandler.OutboundEvent.closeGracefully - channel.triggerUserOutboundEvent(event, promise: nil) - } - } - } - } - - case .failure(let error): - // Connect failed, this connection is no longer useful. - self.state.withLock { $0.closed() } - self.finishStreams(withEvent: .connectFailed(error)) - } - } - - /// Gracefully close the connection. - package func close() { - self.input.continuation.yield(.close) - } - - /// Make a stream using the connection if it's connected. - /// - /// - Parameter descriptor: A descriptor of the method to create a stream for. - /// - Returns: The open stream. - package func makeStream( - descriptor: MethodDescriptor, - options: CallOptions - ) async throws -> Stream { - let (multiplexer, scheme) = try self.state.withLock { state in - switch state { - case .connected(let connected): - return (connected.multiplexer, connected.scheme) - case .notConnected, .closing, .closed: - throw RPCError(code: .unavailable, message: "subchannel isn't ready") - } - } - - let compression: CompressionAlgorithm - if let override = options.compression { - compression = self.enabledCompression.contains(override) ? override : .none - } else { - compression = self.defaultCompression - } - - let maxRequestSize = options.maxRequestMessageBytes ?? Self.defaultMaxRequestMessageSizeBytes - - do { - let stream = try await multiplexer.openStream { channel in - channel.eventLoop.makeCompletedFuture { - let streamHandler = GRPCClientStreamHandler( - methodDescriptor: descriptor, - scheme: scheme, - outboundEncoding: compression, - acceptedEncodings: self.enabledCompression, - maxPayloadSize: maxRequestSize - ) - try channel.pipeline.syncOperations.addHandler(streamHandler) - - return try NIOAsyncChannel( - wrappingChannelSynchronously: channel, - configuration: NIOAsyncChannel.Configuration( - isOutboundHalfClosureEnabled: true, - inboundType: RPCResponsePart.self, - outboundType: RPCRequestPart.self - ) - ) - } - } - - return Stream(wrapping: stream, descriptor: descriptor) - } catch { - throw RPCError(code: .unavailable, message: "subchannel is unavailable", cause: error) - } - } - - private func consumeConnectionEvents( - _ connectionEvents: NIOAsyncChannelInboundStream - ) async { - // The connection becomes 'ready' when the initial HTTP/2 SETTINGS frame is received. - // Establishing a TCP connection is insufficient as the TLS handshake may not complete or the - // server might not be configured for gRPC or HTTP/2. - // - // This state is tracked here so that if the connection events sequence finishes and the - // connection never became ready then the connection can report that the connect failed. - var isReady = false - - func makeNeverReadyError(cause: (any Error)?) -> RPCError { - return RPCError( - code: .unavailable, - message: """ - The server accepted the TCP connection but closed the connection before completing \ - the HTTP/2 connection preface. - """, - cause: cause - ) - } - - do { - var channelCloseReason: ClientConnectionEvent.CloseReason? - - for try await connectionEvent in connectionEvents { - switch connectionEvent { - case .ready: - isReady = true - self.event.continuation.yield(.connectSucceeded) - - case .closing(let reason): - self.state.withLock { $0.closing() } - - switch reason { - case .goAway(let errorCode, let reason): - // The connection will close at some point soon, yield a notification for this - // because the close might not be imminent and this could result in address resolution. - self.event.continuation.yield(.goingAway(errorCode, reason)) - case .idle, .keepaliveExpired, .initiatedLocally, .unexpected: - // The connection will be closed imminently in these cases there's no need to do - // anything. - () - } - - // Take the reason with the highest precedence. A GOAWAY may be superseded by user - // closing, for example. - if channelCloseReason.map({ reason.precedence > $0.precedence }) ?? true { - channelCloseReason = reason - } - } - } - - let finalEvent: Event - if isReady { - let connectionCloseReason: CloseReason - switch channelCloseReason { - case .keepaliveExpired: - connectionCloseReason = .keepaliveTimeout - - case .idle: - // Connection became idle, that's fine. - connectionCloseReason = .idleTimeout - - case .goAway: - // Remote peer told us to GOAWAY. - connectionCloseReason = .remote - - case .initiatedLocally: - // Shutdown was initiated locally. - connectionCloseReason = .initiatedLocally - - case .unexpected(let error, let isIdle): - let error = RPCError( - code: .unavailable, - message: "The TCP connection was dropped unexpectedly.", - cause: error - ) - connectionCloseReason = .error(error, wasIdle: isIdle) - - case .none: - let error = RPCError( - code: .unavailable, - message: "The TCP connection was dropped unexpectedly.", - cause: nil - ) - connectionCloseReason = .error(error, wasIdle: true) - } - - finalEvent = .closed(connectionCloseReason) - } else { - // The connection never became ready, this therefore counts as a failed connect attempt. - finalEvent = .connectFailed(makeNeverReadyError(cause: nil)) - } - - // The connection events sequence has finished: the connection is now closed. - self.state.withLock { $0.closed() } - self.finishStreams(withEvent: finalEvent) - } catch { - let finalEvent: Event - - if isReady { - // Any error must come from consuming the inbound channel meaning that the connection - // must be borked, wrap it up and close. - let rpcError = RPCError(code: .unavailable, message: "connection closed", cause: error) - finalEvent = .closed(.error(rpcError, wasIdle: true)) - } else { - // The connection never became ready, this therefore counts as a failed connect attempt. - finalEvent = .connectFailed(makeNeverReadyError(cause: error)) - } - - self.state.withLock { $0.closed() } - self.finishStreams(withEvent: finalEvent) - } - } - - private func finishStreams(withEvent event: Event) { - self.event.continuation.yield(event) - self.event.continuation.finish() - self.input.continuation.finish() - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension Connection { - package struct Stream { - package typealias Inbound = NIOAsyncChannelInboundStream - - package struct Outbound: ClosableRPCWriterProtocol { - package typealias Element = RPCRequestPart - - private let requestWriter: NIOAsyncChannelOutboundWriter - private let http2Stream: NIOAsyncChannel - - fileprivate init( - requestWriter: NIOAsyncChannelOutboundWriter, - http2Stream: NIOAsyncChannel - ) { - self.requestWriter = requestWriter - self.http2Stream = http2Stream - } - - package func write(_ element: RPCRequestPart) async throws { - try await self.requestWriter.write(element) - } - - package func write(contentsOf elements: some Sequence) async throws { - try await self.requestWriter.write(contentsOf: elements) - } - - package func finish() { - self.requestWriter.finish() - } - - package func finish(throwing error: any Error) { - // Fire the error inbound; this fails the inbound writer. - self.http2Stream.channel.pipeline.fireErrorCaught(error) - } - } - - let descriptor: MethodDescriptor - - private let http2Stream: NIOAsyncChannel - - init( - wrapping stream: NIOAsyncChannel, - descriptor: MethodDescriptor - ) { - self.http2Stream = stream - self.descriptor = descriptor - } - - package func execute( - _ closure: (_ inbound: Inbound, _ outbound: Outbound) async throws -> T - ) async throws -> T where T: Sendable { - try await self.http2Stream.executeThenClose { inbound, outbound in - return try await closure( - inbound, - Outbound(requestWriter: outbound, http2Stream: self.http2Stream) - ) - } - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension Connection { - private enum State: Sendable { - /// The connection is idle or connecting. - case notConnected - /// A TCP connection has been established with the remote peer. However, the connection may not - /// be ready to use yet. - case connected(Connected) - /// The connection has started to close. This may be initiated locally or by the remote. - case closing - /// The connection has closed. This is a terminal state. - case closed - - struct Connected: Sendable { - /// The connection channel. - var channel: NIOAsyncChannel - /// Multiplexer for creating HTTP/2 streams. - var multiplexer: NIOHTTP2Handler.AsyncStreamMultiplexer - /// Whether the connection is plaintext, `false` implies TLS is being used. - var scheme: Scheme - - init(_ connection: HTTP2Connection) { - self.channel = connection.channel - self.multiplexer = connection.multiplexer - self.scheme = connection.isPlaintext ? .http : .https - } - } - - mutating func connected(_ channel: HTTP2Connection) { - switch self { - case .notConnected: - self = .connected(State.Connected(channel)) - case .connected, .closing, .closed: - fatalError("Invalid state: 'run()' must only be called once") - } - } - - mutating func beginClosing() -> NIOAsyncChannel? { - switch self { - case .notConnected: - fatalError("Invalid state: 'run()' must be called first") - case .connected(let connected): - self = .closing - return connected.channel - case .closing, .closed: - return nil - } - } - - mutating func closing() { - switch self { - case .notConnected: - // Not reachable: happens as a result of a connection event, that can only happen if - // the connection has started (i.e. must be in the 'connected' state or later). - fatalError("Invalid state") - case .connected: - self = .closing - case .closing, .closed: - () - } - } - - mutating func closed() { - self = .closed - } - } -} - -extension ClientConnectionEvent.CloseReason { - fileprivate var precedence: Int { - switch self { - case .unexpected: - return -1 - case .goAway: - return 0 - case .idle: - return 1 - case .keepaliveExpired: - return 2 - case .initiatedLocally: - return 3 - } - } -} diff --git a/Sources/GRPCHTTP2Core/Client/Connection/ConnectionBackoff.swift b/Sources/GRPCHTTP2Core/Client/Connection/ConnectionBackoff.swift deleted file mode 100644 index 8e0ed5e66..000000000 --- a/Sources/GRPCHTTP2Core/Client/Connection/ConnectionBackoff.swift +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -package struct ConnectionBackoff { - package var initial: Duration - package var max: Duration - package var multiplier: Double - package var jitter: Double - - package init(initial: Duration, max: Duration, multiplier: Double, jitter: Double) { - self.initial = initial - self.max = max - self.multiplier = multiplier - self.jitter = jitter - } - - package func makeIterator() -> Iterator { - return Iterator(self) - } - - // Deliberately not conforming to `IteratorProtocol` as `next()` never returns `nil` which - // isn't expressible via `IteratorProtocol`. - package struct Iterator { - private var isInitial: Bool - private var currentBackoffSeconds: Double - - private let jitter: Double - private let multiplier: Double - private let maxBackoffSeconds: Double - - init(_ backoff: ConnectionBackoff) { - self.isInitial = true - self.currentBackoffSeconds = Self.seconds(from: backoff.initial) - self.jitter = backoff.jitter - self.multiplier = backoff.multiplier - self.maxBackoffSeconds = Self.seconds(from: backoff.max) - } - - private static func seconds(from duration: Duration) -> Double { - var seconds = Double(duration.components.seconds) - seconds += Double(duration.components.attoseconds) / 1e18 - return seconds - } - - private static func duration(from seconds: Double) -> Duration { - let nanoseconds = seconds * 1e9 - let wholeNanos = Int64(nanoseconds) - return .nanoseconds(wholeNanos) - } - - package mutating func next() -> Duration { - // The initial backoff doesn't get jittered. - if self.isInitial { - self.isInitial = false - return Self.duration(from: self.currentBackoffSeconds) - } - - // Scale up the last backoff. - self.currentBackoffSeconds *= self.multiplier - - // Limit it to the max backoff. - if self.currentBackoffSeconds > self.maxBackoffSeconds { - self.currentBackoffSeconds = self.maxBackoffSeconds - } - - let backoff = self.currentBackoffSeconds - let jitter = Double.random(in: -(self.jitter * backoff) ... self.jitter * backoff) - let jitteredBackoff = backoff + jitter - - return Self.duration(from: jitteredBackoff) - } - } -} diff --git a/Sources/GRPCHTTP2Core/Client/Connection/ConnectionFactory.swift b/Sources/GRPCHTTP2Core/Client/Connection/ConnectionFactory.swift deleted file mode 100644 index c56e507db..000000000 --- a/Sources/GRPCHTTP2Core/Client/Connection/ConnectionFactory.swift +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -package import NIOCore -package import NIOHTTP2 -internal import NIOPosix - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -package protocol HTTP2Connector: Sendable { - func establishConnection(to address: SocketAddress) async throws -> HTTP2Connection -} - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -package struct HTTP2Connection: Sendable { - /// The underlying TCP connection wrapped up for use with gRPC. - var channel: NIOAsyncChannel - - /// An HTTP/2 stream multiplexer. - var multiplexer: NIOHTTP2Handler.AsyncStreamMultiplexer - - /// Whether the connection is insecure (i.e. plaintext). - var isPlaintext: Bool - - package init( - channel: NIOAsyncChannel, - multiplexer: NIOHTTP2Handler.AsyncStreamMultiplexer, - isPlaintext: Bool - ) { - self.channel = channel - self.multiplexer = multiplexer - self.isPlaintext = isPlaintext - } -} diff --git a/Sources/GRPCHTTP2Core/Client/Connection/ConnectivityState.swift b/Sources/GRPCHTTP2Core/Client/Connection/ConnectivityState.swift deleted file mode 100644 index 6f4b000ca..000000000 --- a/Sources/GRPCHTTP2Core/Client/Connection/ConnectivityState.swift +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -package enum ConnectivityState: Sendable, Hashable { - /// This channel isn't trying to create a connection because of a lack of new or pending RPCs. - /// - /// New streams may be created in this state. Doing so will cause the channel to enter the - /// connecting state. - case idle - - /// The channel is trying to establish a connection and is waiting to make progress on one of the - /// steps involved in name resolution, TCP connection establishment or TLS handshake. - case connecting - - /// The channel has successfully established a connection all the way through TLS handshake (or - /// equivalent) and protocol-level (HTTP/2, etc) handshaking. - case ready - - /// There has been some transient failure (such as a TCP 3-way handshake timing out or a socket - /// error). Channels in this state will eventually switch to the ``connecting`` state and try to - /// establish a connection again. Since retries are done with exponential backoff, channels that - /// fail to connect will start out spending very little time in this state but as the attempts - /// fail repeatedly, the channel will spend increasingly large amounts of time in this state. - case transientFailure - - /// This channel has started shutting down. Any new RPCs should fail immediately. Pending RPCs - /// may continue running until the application cancels them. Channels may enter this state either - /// because the application explicitly requested a shutdown or if a non-recoverable error has - /// happened during attempts to connect. Channels that have entered this state will never leave - /// this state. - case shutdown -} diff --git a/Sources/GRPCHTTP2Core/Client/Connection/GRPCChannel.swift b/Sources/GRPCHTTP2Core/Client/Connection/GRPCChannel.swift deleted file mode 100644 index 7be28da30..000000000 --- a/Sources/GRPCHTTP2Core/Client/Connection/GRPCChannel.swift +++ /dev/null @@ -1,957 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -private import DequeModule -package import GRPCCore -private import Synchronization - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -package final class GRPCChannel: ClientTransport { - private enum Input: Sendable { - /// Close the channel, if possible. - case close - /// Handle the result of a name resolution. - case handleResolutionResult(NameResolutionResult) - /// Handle the event from the underlying connection object. - case handleLoadBalancerEvent(LoadBalancerEvent, LoadBalancerID) - } - - /// Events which can happen to the channel. - private let _connectivityState: - ( - stream: AsyncStream, - continuation: AsyncStream.Continuation - ) - - /// Inputs which this channel should react to. - private let input: (stream: AsyncStream, continuation: AsyncStream.Continuation) - - /// A resolver providing resolved names to the channel. - private let resolver: NameResolver - - /// The state of the channel. - private let state: Mutex - - /// The maximum number of times to attempt to create a stream per RPC. - /// - /// This is the value used by other gRPC implementations. - private static let maxStreamCreationAttempts = 5 - - /// A factory for connections. - private let connector: any HTTP2Connector - - /// The connection backoff configuration used by the subchannel when establishing a connection. - private let backoff: ConnectionBackoff - - /// The default compression algorithm used for requests. - private let defaultCompression: CompressionAlgorithm - - /// The set of enabled compression algorithms. - private let enabledCompression: CompressionAlgorithmSet - - /// The default service config to use. - /// - /// Used when the resolver doesn't provide one. - private let defaultServiceConfig: ServiceConfig - - // These are both read frequently and updated infrequently so may be a bottleneck. - private let _methodConfig: Mutex - private let _retryThrottle: Mutex - - package init( - resolver: NameResolver, - connector: any HTTP2Connector, - config: Config, - defaultServiceConfig: ServiceConfig - ) { - self.resolver = resolver - self.state = Mutex(StateMachine()) - self._connectivityState = AsyncStream.makeStream() - self.input = AsyncStream.makeStream() - self.connector = connector - - self.backoff = ConnectionBackoff( - initial: config.backoff.initial, - max: config.backoff.max, - multiplier: config.backoff.multiplier, - jitter: config.backoff.jitter - ) - self.defaultCompression = config.compression.algorithm - self.enabledCompression = config.compression.enabledAlgorithms - self.defaultServiceConfig = defaultServiceConfig - - let throttle = defaultServiceConfig.retryThrottling.map { RetryThrottle(policy: $0) } - self._retryThrottle = Mutex(throttle) - - let methodConfig = MethodConfigs(serviceConfig: defaultServiceConfig) - self._methodConfig = Mutex(methodConfig) - } - - /// The connectivity state of the channel. - package var connectivityState: AsyncStream { - self._connectivityState.stream - } - - /// Returns a throttle which gRPC uses to determine whether retries can be executed. - package var retryThrottle: RetryThrottle? { - self._retryThrottle.withLock { $0 } - } - - /// Returns the configuration for a given method. - /// - /// - Parameter descriptor: The method to lookup configuration for. - /// - Returns: Configuration for the method, if it exists. - package func config(forMethod descriptor: MethodDescriptor) -> MethodConfig? { - self._methodConfig.withLock { $0[descriptor] } - } - - /// Establishes and maintains a connection to the remote destination. - package func connect() async { - self.state.withLock { $0.start() } - self._connectivityState.continuation.yield(.idle) - - await withDiscardingTaskGroup { group in - var iterator: Optional.AsyncIterator> - - // The resolver can either push or pull values. If it pushes values the channel should - // listen for new results. Otherwise the channel will pull values as and when necessary. - switch self.resolver.updateMode.value { - case .push: - iterator = nil - - let handle = group.addCancellableTask { - do { - for try await result in self.resolver.names { - self.input.continuation.yield(.handleResolutionResult(result)) - } - self.beginGracefulShutdown() - } catch { - self.beginGracefulShutdown() - } - } - - // When the channel is closed gracefully, the task group running the load balancer mustn't - // be cancelled (otherwise in-flight RPCs would fail), but the push based resolver will - // continue indefinitely. Store its handle and cancel it on close when closing the channel. - self.state.withLock { state in - state.setNameResolverTaskHandle(handle) - } - - case .pull: - iterator = self.resolver.names.makeAsyncIterator() - await self.resolve(iterator: &iterator, in: &group) - } - - // Resolver is setup, start handling events. - for await input in self.input.stream { - switch input { - case .close: - self.handleClose(in: &group) - - case .handleResolutionResult(let result): - self.handleNameResolutionResult(result, in: &group) - - case .handleLoadBalancerEvent(let event, let id): - await self.handleLoadBalancerEvent( - event, - loadBalancerID: id, - in: &group, - iterator: &iterator - ) - } - } - } - - if Task.isCancelled { - self._connectivityState.continuation.finish() - } - } - - /// Signal to the transport that no new streams may be created and that connections should be - /// closed when all streams are closed. - package func beginGracefulShutdown() { - self.input.continuation.yield(.close) - } - - /// Opens a stream using the transport, and uses it as input into a user-provided closure. - package func withStream( - descriptor: MethodDescriptor, - options: CallOptions, - _ closure: (_ stream: RPCStream) async throws -> T - ) async throws -> T { - // Merge options from the call with those from the service config. - let methodConfig = self.config(forMethod: descriptor) - var options = options - options.formUnion(with: methodConfig) - - for attempt in 1 ... Self.maxStreamCreationAttempts { - switch await self.makeStream(descriptor: descriptor, options: options) { - case .created(let stream): - return try await stream.execute { inbound, outbound in - let rpcStream = RPCStream( - descriptor: stream.descriptor, - inbound: RPCAsyncSequence(wrapping: inbound), - outbound: RPCWriter.Closable(wrapping: outbound) - ) - return try await closure(rpcStream) - } - - case .tryAgain(let error): - if error is CancellationError || attempt == Self.maxStreamCreationAttempts { - throw error - } else { - continue - } - - case .stopTrying(let error): - throw error - } - } - - fatalError("Internal inconsistency") - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension GRPCChannel { - package struct Config: Sendable { - /// Configuration for HTTP/2 connections. - package var http2: HTTP2ClientTransport.Config.HTTP2 - - /// Configuration for backoff used when establishing a connection. - package var backoff: HTTP2ClientTransport.Config.Backoff - - /// Configuration for connection management. - package var connection: HTTP2ClientTransport.Config.Connection - - /// Compression configuration. - package var compression: HTTP2ClientTransport.Config.Compression - - package init( - http2: HTTP2ClientTransport.Config.HTTP2, - backoff: HTTP2ClientTransport.Config.Backoff, - connection: HTTP2ClientTransport.Config.Connection, - compression: HTTP2ClientTransport.Config.Compression - ) { - self.http2 = http2 - self.backoff = backoff - self.connection = connection - self.compression = compression - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension GRPCChannel { - enum MakeStreamResult { - /// A stream was created, use it. - case created(Connection.Stream) - /// An error occurred while trying to create a stream, try again if possible. - case tryAgain(any Error) - /// An unrecoverable error occurred (e.g. the channel is closed), fail the RPC and don't retry. - case stopTrying(any Error) - } - - private func makeStream( - descriptor: MethodDescriptor, - options: CallOptions - ) async -> MakeStreamResult { - let waitForReady = options.waitForReady ?? true - switch self.state.withLock({ $0.makeStream(waitForReady: waitForReady) }) { - case .useLoadBalancer(let loadBalancer): - return await self.makeStream( - descriptor: descriptor, - options: options, - loadBalancer: loadBalancer - ) - - case .joinQueue: - do { - let loadBalancer = try await self.enqueue(waitForReady: waitForReady) - return await self.makeStream( - descriptor: descriptor, - options: options, - loadBalancer: loadBalancer - ) - } catch { - // All errors from enqueue are non-recoverable: either the channel is shutting down or - // the request has been cancelled. - return .stopTrying(error) - } - - case .failRPC: - return .stopTrying(RPCError(code: .unavailable, message: "channel isn't ready")) - } - } - - private func makeStream( - descriptor: MethodDescriptor, - options: CallOptions, - loadBalancer: LoadBalancer - ) async -> MakeStreamResult { - guard let subchannel = loadBalancer.pickSubchannel() else { - return .tryAgain(RPCError(code: .unavailable, message: "channel isn't ready")) - } - - let methodConfig = self.config(forMethod: descriptor) - var options = options - options.formUnion(with: methodConfig) - - do { - let stream = try await subchannel.makeStream(descriptor: descriptor, options: options) - return .created(stream) - } catch { - return .tryAgain(error) - } - } - - private func enqueue(waitForReady: Bool) async throws -> LoadBalancer { - let id = QueueEntryID() - return try await withTaskCancellationHandler { - try await withCheckedThrowingContinuation { continuation in - if Task.isCancelled { - continuation.resume(throwing: CancellationError()) - return - } - - let enqueued = self.state.withLock { state in - state.enqueue(continuation: continuation, waitForReady: waitForReady, id: id) - } - - // Not enqueued because the channel is shutdown or shutting down. - if !enqueued { - let error = RPCError(code: .unavailable, message: "channel is shutdown") - continuation.resume(throwing: error) - } - } - } onCancel: { - let continuation = self.state.withLock { state in - state.dequeueContinuation(id: id) - } - - continuation?.resume(throwing: CancellationError()) - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension GRPCChannel { - private func handleClose(in group: inout DiscardingTaskGroup) { - switch self.state.withLock({ $0.close() }) { - case .close(let current, let next, let resolver, let continuations): - resolver?.cancel() - current.close() - next?.close() - for continuation in continuations { - continuation.resume(throwing: RPCError(code: .unavailable, message: "channel is closed")) - } - self._connectivityState.continuation.yield(.shutdown) - - case .cancelAll(let continuations): - for continuation in continuations { - continuation.resume(throwing: RPCError(code: .unavailable, message: "channel is closed")) - } - self._connectivityState.continuation.yield(.shutdown) - group.cancelAll() - - case .none: - () - } - } - - private func handleNameResolutionResult( - _ result: NameResolutionResult, - in group: inout DiscardingTaskGroup - ) { - // Ignore empty endpoint lists. - if result.endpoints.isEmpty { return } - - switch result.serviceConfig ?? .success(self.defaultServiceConfig) { - case .success(let config): - // Update per RPC configuration. - let methodConfig = MethodConfigs(serviceConfig: config) - self._methodConfig.withLock { $0 = methodConfig } - - let retryThrottle = config.retryThrottling.map { RetryThrottle(policy: $0) } - self._retryThrottle.withLock { $0 = retryThrottle } - - // Update the load balancer. - self.updateLoadBalancer(serviceConfig: config, endpoints: result.endpoints, in: &group) - - case .failure: - self.beginGracefulShutdown() - } - } - - enum SupportedLoadBalancerConfig { - case roundRobin - case pickFirst(ServiceConfig.LoadBalancingConfig.PickFirst) - - init?(_ config: ServiceConfig.LoadBalancingConfig) { - if let pickFirst = config.pickFirst { - self = .pickFirst(pickFirst) - } else if config.roundRobin != nil { - self = .roundRobin - } else { - return nil - } - } - - func matches(loadBalancer: LoadBalancer) -> Bool { - switch (self, loadBalancer) { - case (.roundRobin, .roundRobin): - return true - case (.pickFirst, .pickFirst): - return true - case (.roundRobin, .pickFirst), - (.pickFirst, .roundRobin): - return false - } - } - } - - private func updateLoadBalancer( - serviceConfig: ServiceConfig, - endpoints: [Endpoint], - in group: inout DiscardingTaskGroup - ) { - assert(!endpoints.isEmpty, "endpoints must be non-empty") - - // Find the first supported config. - var configFromServiceConfig: SupportedLoadBalancerConfig? - for config in serviceConfig.loadBalancingConfig { - if let config = SupportedLoadBalancerConfig(config) { - configFromServiceConfig = config - break - } - } - - let onUpdatePolicy: GRPCChannel.StateMachine.OnChangeLoadBalancer - var endpoints = endpoints - - // Fallback to pick-first if no other config applies. - let loadBalancerConfig = configFromServiceConfig ?? .pickFirst(.init(shuffleAddressList: false)) - switch loadBalancerConfig { - case .roundRobin: - onUpdatePolicy = self.state.withLock { state in - state.changeLoadBalancerKind(to: loadBalancerConfig) { - let loadBalancer = RoundRobinLoadBalancer( - connector: self.connector, - backoff: self.backoff, - defaultCompression: self.defaultCompression, - enabledCompression: self.enabledCompression - ) - return .roundRobin(loadBalancer) - } - } - - case .pickFirst(let pickFirst): - if pickFirst.shuffleAddressList { - endpoints[0].addresses.shuffle() - } - - onUpdatePolicy = self.state.withLock { state in - state.changeLoadBalancerKind(to: loadBalancerConfig) { - let loadBalancer = PickFirstLoadBalancer( - connector: self.connector, - backoff: self.backoff, - defaultCompression: self.defaultCompression, - enabledCompression: self.enabledCompression - ) - return .pickFirst(loadBalancer) - } - } - } - - self.handleLoadBalancerChange(onUpdatePolicy, endpoints: endpoints, in: &group) - } - - private func handleLoadBalancerChange( - _ update: StateMachine.OnChangeLoadBalancer, - endpoints: [Endpoint], - in group: inout DiscardingTaskGroup - ) { - assert(!endpoints.isEmpty, "endpoints must be non-empty") - - switch update { - case .runLoadBalancer(let new, let old): - old?.close() - switch new { - case .roundRobin(let loadBalancer): - loadBalancer.updateAddresses(endpoints) - case .pickFirst(let loadBalancer): - loadBalancer.updateEndpoint(endpoints.first!) - } - - group.addTask { - await new.run() - } - - group.addTask { - for await event in new.events { - self.input.continuation.yield(.handleLoadBalancerEvent(event, new.id)) - } - } - - case .updateLoadBalancer(let existing): - switch existing { - case .roundRobin(let loadBalancer): - loadBalancer.updateAddresses(endpoints) - case .pickFirst(let loadBalancer): - loadBalancer.updateEndpoint(endpoints.first!) - } - - case .none: - () - } - } - - private func handleLoadBalancerEvent( - _ event: LoadBalancerEvent, - loadBalancerID: LoadBalancerID, - in group: inout DiscardingTaskGroup, - iterator: inout RPCAsyncSequence.AsyncIterator? - ) async { - switch event { - case .connectivityStateChanged(let connectivityState): - let actions = self.state.withLock { state in - state.loadBalancerStateChanged(to: connectivityState, id: loadBalancerID) - } - - if let newState = actions.publishState { - self._connectivityState.continuation.yield(newState) - } - - if let subchannel = actions.close { - subchannel.close() - } - - if let resumable = actions.resumeContinuations { - for continuation in resumable.continuations { - continuation.resume(with: resumable.result) - } - } - - if actions.finish { - // Fully closed. - self._connectivityState.continuation.finish() - self.input.continuation.finish() - } - - case .requiresNameResolution: - await self.resolve(iterator: &iterator, in: &group) - } - } - - private func resolve( - iterator: inout RPCAsyncSequence.AsyncIterator?, - in group: inout DiscardingTaskGroup - ) async { - guard var iterator = iterator else { return } - - do { - if let result = try await iterator.next() { - self.handleNameResolutionResult(result, in: &group) - } else { - self.beginGracefulShutdown() - } - } catch { - self.beginGracefulShutdown() - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension GRPCChannel { - struct StateMachine { - enum State { - case notRunning(NotRunning) - case running(Running) - case stopping(Stopping) - case stopped - case _modifying - - struct NotRunning { - /// Queue of requests waiting for a load-balancer. - var queue: RequestQueue - /// A handle to the name resolver task. - var nameResolverHandle: CancellableTaskHandle? - - init() { - self.queue = RequestQueue() - } - } - - struct Running { - /// The connectivity state of the channel. - var connectivityState: ConnectivityState - /// The load-balancer currently in use. - var current: LoadBalancer - /// The next load-balancer to use. This will be promoted to `current` when it enters the - /// ready state. - var next: LoadBalancer? - /// Previously created load-balancers which are in the process of shutting down. - var past: [LoadBalancerID: LoadBalancer] - /// Queue of requests wait for a load-balancer. - var queue: RequestQueue - /// A handle to the name resolver task. - var nameResolverHandle: CancellableTaskHandle? - - init( - from state: NotRunning, - loadBalancer: LoadBalancer - ) { - self.connectivityState = .idle - self.current = loadBalancer - self.next = nil - self.past = [:] - self.queue = state.queue - self.nameResolverHandle = state.nameResolverHandle - } - } - - struct Stopping { - /// Previously created load-balancers which are in the process of shutting down. - var past: [LoadBalancerID: LoadBalancer] - - init(from state: Running) { - self.past = state.past - } - - init(loadBalancers: [LoadBalancerID: LoadBalancer]) { - self.past = loadBalancers - } - } - } - - /// The current state. - private var state: State - /// Whether the channel is running. - private var running: Bool - - init() { - self.state = .notRunning(State.NotRunning()) - self.running = false - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension GRPCChannel.StateMachine { - mutating func start() { - precondition(!self.running, "channel must only be started once") - self.running = true - } - - mutating func setNameResolverTaskHandle(_ handle: CancellableTaskHandle) { - switch self.state { - case .notRunning(var state): - state.nameResolverHandle = handle - self.state = .notRunning(state) - case .running, .stopping, .stopped, ._modifying: - fatalError("Invalid state") - } - } - - enum OnChangeLoadBalancer { - case runLoadBalancer(LoadBalancer, stop: LoadBalancer?) - case updateLoadBalancer(LoadBalancer) - case none - } - - mutating func changeLoadBalancerKind( - to newLoadBalancerKind: GRPCChannel.SupportedLoadBalancerConfig, - _ makeLoadBalancer: () -> LoadBalancer - ) -> OnChangeLoadBalancer { - let onChangeLoadBalancer: OnChangeLoadBalancer - - switch self.state { - case .notRunning(let state): - let loadBalancer = makeLoadBalancer() - let state = State.Running(from: state, loadBalancer: loadBalancer) - self.state = .running(state) - onChangeLoadBalancer = .runLoadBalancer(state.current, stop: nil) - - case .running(var state): - self.state = ._modifying - - if let next = state.next { - if newLoadBalancerKind.matches(loadBalancer: next) { - onChangeLoadBalancer = .updateLoadBalancer(next) - } else { - // The 'next' didn't become ready in time. Close it and replace it with a load-balancer - // of the next kind. - let nextNext = makeLoadBalancer() - let previous = state.next - state.next = nextNext - state.past[next.id] = next - onChangeLoadBalancer = .runLoadBalancer(nextNext, stop: previous) - } - } else { - if newLoadBalancerKind.matches(loadBalancer: state.current) { - onChangeLoadBalancer = .updateLoadBalancer(state.current) - } else { - // Create the 'next' load-balancer, it'll replace 'current' when it becomes ready. - let next = makeLoadBalancer() - state.next = next - onChangeLoadBalancer = .runLoadBalancer(next, stop: nil) - } - } - - self.state = .running(state) - - case .stopping, .stopped: - onChangeLoadBalancer = .none - - case ._modifying: - fatalError("Invalid state") - } - - return onChangeLoadBalancer - } - - struct ConnectivityStateChangeActions { - var close: LoadBalancer? = nil - var publishState: ConnectivityState? = nil - var resumeContinuations: ResumableContinuations? = nil - var finish: Bool = false - - struct ResumableContinuations { - var continuations: [CheckedContinuation] - var result: Result - } - } - - mutating func loadBalancerStateChanged( - to connectivityState: ConnectivityState, - id: LoadBalancerID - ) -> ConnectivityStateChangeActions { - var actions = ConnectivityStateChangeActions() - - switch self.state { - case .running(var state): - self.state = ._modifying - - if id == state.current.id { - // No change in state, ignore. - if state.connectivityState == connectivityState { - self.state = .running(state) - break - } - - state.connectivityState = connectivityState - actions.publishState = connectivityState - - switch connectivityState { - case .ready: - // Current load-balancer became ready; resume all continuations in the queue. - let continuations = state.queue.removeAll() - actions.resumeContinuations = ConnectivityStateChangeActions.ResumableContinuations( - continuations: continuations, - result: .success(state.current) - ) - - case .transientFailure, .shutdown: // shutdown includes shutting down - // Current load-balancer failed. Remove all the 'fast-failing' continuations in the - // queue, these are RPCs which set the 'wait for ready' option to false. The rest of - // the entries in the queue will wait for a load-balancer to become ready. - let continuations = state.queue.removeFastFailingEntries() - actions.resumeContinuations = ConnectivityStateChangeActions.ResumableContinuations( - continuations: continuations, - result: .failure(RPCError(code: .unavailable, message: "channel isn't ready")) - ) - - case .idle, .connecting: - () // Ignore. - } - } else if let next = state.next, next.id == id { - // State change came from the next LB, if it's now ready promote it to be the current. - switch connectivityState { - case .ready: - // Next load-balancer is ready, promote it to current. - let previous = state.current - state.past[previous.id] = previous - state.current = next - state.next = nil - - actions.close = previous - - if state.connectivityState != connectivityState { - actions.publishState = connectivityState - } - - actions.resumeContinuations = ConnectivityStateChangeActions.ResumableContinuations( - continuations: state.queue.removeAll(), - result: .success(next) - ) - - case .idle, .connecting, .transientFailure, .shutdown: - () - } - } - - self.state = .running(state) - - case .stopping(var state): - self.state = ._modifying - - // Remove the load balancer if it's now shutdown. - switch connectivityState { - case .shutdown: - state.past.removeValue(forKey: id) - case .idle, .connecting, .ready, .transientFailure: - () - } - - // If that was the last load-balancer then finish the input streams so that the channel - // eventually finishes. - if state.past.isEmpty { - actions.finish = true - self.state = .stopped - } else { - self.state = .stopping(state) - } - - case .notRunning, .stopped: - () - - case ._modifying: - fatalError("Invalid state") - } - - return actions - } - - enum OnMakeStream { - /// Use the given load-balancer to make a stream. - case useLoadBalancer(LoadBalancer) - /// Join the queue and wait until a load-balancer becomes ready. - case joinQueue - /// Fail the stream request, the channel isn't in a suitable state. - case failRPC - } - - func makeStream(waitForReady: Bool) -> OnMakeStream { - let onMakeStream: OnMakeStream - - switch self.state { - case .notRunning: - onMakeStream = .joinQueue - - case .running(let state): - switch state.connectivityState { - case .idle, .connecting: - onMakeStream = .joinQueue - case .ready: - onMakeStream = .useLoadBalancer(state.current) - case .transientFailure: - onMakeStream = waitForReady ? .joinQueue : .failRPC - case .shutdown: - onMakeStream = .failRPC - } - - case .stopping, .stopped: - onMakeStream = .failRPC - - case ._modifying: - fatalError("Invalid state") - } - - return onMakeStream - } - - mutating func enqueue( - continuation: CheckedContinuation, - waitForReady: Bool, - id: QueueEntryID - ) -> Bool { - switch self.state { - case .notRunning(var state): - self.state = ._modifying - state.queue.append(continuation: continuation, waitForReady: waitForReady, id: id) - self.state = .notRunning(state) - return true - case .running(var state): - self.state = ._modifying - state.queue.append(continuation: continuation, waitForReady: waitForReady, id: id) - self.state = .running(state) - return true - case .stopping, .stopped: - return false - case ._modifying: - fatalError("Invalid state") - } - } - - mutating func dequeueContinuation( - id: QueueEntryID - ) -> CheckedContinuation? { - switch self.state { - case .notRunning(var state): - self.state = ._modifying - let continuation = state.queue.removeEntry(withID: id) - self.state = .notRunning(state) - return continuation - - case .running(var state): - self.state = ._modifying - let continuation = state.queue.removeEntry(withID: id) - self.state = .running(state) - return continuation - - case .stopping, .stopped: - return nil - - case ._modifying: - fatalError("Invalid state") - } - } - - enum OnClose { - case none - case cancelAll([RequestQueue.Continuation]) - case close(LoadBalancer, LoadBalancer?, CancellableTaskHandle?, [RequestQueue.Continuation]) - } - - mutating func close() -> OnClose { - let onClose: OnClose - - switch self.state { - case .notRunning(var state): - self.state = .stopped - onClose = .cancelAll(state.queue.removeAll()) - - case .running(var state): - let continuations = state.queue.removeAll() - onClose = .close(state.current, state.next, state.nameResolverHandle, continuations) - - state.past[state.current.id] = state.current - if let next = state.next { - state.past[next.id] = next - } - - self.state = .stopping(State.Stopping(loadBalancers: state.past)) - - case .stopping, .stopped: - onClose = .none - - case ._modifying: - fatalError("Invalid state") - } - - return onClose - } -} diff --git a/Sources/GRPCHTTP2Core/Client/Connection/LoadBalancers/LoadBalancer.swift b/Sources/GRPCHTTP2Core/Client/Connection/LoadBalancers/LoadBalancer.swift deleted file mode 100644 index 419094aba..000000000 --- a/Sources/GRPCHTTP2Core/Client/Connection/LoadBalancers/LoadBalancer.swift +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -package enum LoadBalancer: Sendable { - case roundRobin(RoundRobinLoadBalancer) - case pickFirst(PickFirstLoadBalancer) -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension LoadBalancer { - package init(_ loadBalancer: RoundRobinLoadBalancer) { - self = .roundRobin(loadBalancer) - } - - var id: LoadBalancerID { - switch self { - case .roundRobin(let loadBalancer): - return loadBalancer.id - case .pickFirst(let loadBalancer): - return loadBalancer.id - } - } - - package var events: AsyncStream { - switch self { - case .roundRobin(let loadBalancer): - return loadBalancer.events - case .pickFirst(let loadBalancer): - return loadBalancer.events - } - } - - package func run() async { - switch self { - case .roundRobin(let loadBalancer): - await loadBalancer.run() - case .pickFirst(let loadBalancer): - await loadBalancer.run() - } - } - - package func close() { - switch self { - case .roundRobin(let loadBalancer): - loadBalancer.close() - case .pickFirst(let loadBalancer): - loadBalancer.close() - } - } - - package func pickSubchannel() -> Subchannel? { - switch self { - case .roundRobin(let loadBalancer): - return loadBalancer.pickSubchannel() - case .pickFirst(let loadBalancer): - return loadBalancer.pickSubchannel() - } - } -} diff --git a/Sources/GRPCHTTP2Core/Client/Connection/LoadBalancers/LoadBalancerEvent.swift b/Sources/GRPCHTTP2Core/Client/Connection/LoadBalancers/LoadBalancerEvent.swift deleted file mode 100644 index 439471ac6..000000000 --- a/Sources/GRPCHTTP2Core/Client/Connection/LoadBalancers/LoadBalancerEvent.swift +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -/// Events emitted by load-balancers. -package enum LoadBalancerEvent: Sendable, Hashable { - /// The connectivity state of the subchannel changed. - case connectivityStateChanged(ConnectivityState) - /// The subchannel requests that the load balancer re-resolves names. - case requiresNameResolution -} diff --git a/Sources/GRPCHTTP2Core/Client/Connection/LoadBalancers/PickFirstLoadBalancer.swift b/Sources/GRPCHTTP2Core/Client/Connection/LoadBalancers/PickFirstLoadBalancer.swift deleted file mode 100644 index 31c6d4382..000000000 --- a/Sources/GRPCHTTP2Core/Client/Connection/LoadBalancers/PickFirstLoadBalancer.swift +++ /dev/null @@ -1,610 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -package import GRPCCore -private import Synchronization - -/// A load-balancer which has a single subchannel. -/// -/// This load-balancer starts in an 'idle' state and begins connecting when a set of addresses is -/// provided to it with ``updateEndpoint(_:)``. Repeated calls to ``updateEndpoint(_:)`` will -/// update the subchannel gracefully: RPCs will continue to use the old subchannel until the new -/// subchannel becomes ready. -/// -/// You must call ``close()`` on the load-balancer when it's no longer required. This will move -/// it to the ``ConnectivityState/shutdown`` state: existing RPCs may continue but all subsequent -/// calls to ``makeStream(descriptor:options:)`` will fail. -/// -/// To use this load-balancer you must run it in a task: -/// -/// ```swift -/// await withDiscardingTaskGroup { group in -/// // Run the load-balancer -/// group.addTask { await pickFirst.run() } -/// -/// // Update its endpoint. -/// let endpoint = Endpoint( -/// addresses: [ -/// .ipv4(host: "127.0.0.1", port: 1001), -/// .ipv4(host: "127.0.0.1", port: 1002), -/// .ipv4(host: "127.0.0.1", port: 1003) -/// ] -/// ) -/// pickFirst.updateEndpoint(endpoint) -/// -/// // Consume state update events -/// for await event in pickFirst.events { -/// switch event { -/// case .connectivityStateChanged(.ready): -/// // ... -/// default: -/// // ... -/// } -/// } -/// } -/// ``` -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -package final class PickFirstLoadBalancer: Sendable { - enum Input: Sendable, Hashable { - /// Update the addresses used by the load balancer to the following endpoints. - case updateEndpoint(Endpoint) - /// Close the load balancer. - case close - } - - /// Events which can happen to the load balancer. - private let event: - ( - stream: AsyncStream, - continuation: AsyncStream.Continuation - ) - - /// Inputs which this load balancer should react to. - private let input: (stream: AsyncStream, continuation: AsyncStream.Continuation) - - /// A connector, capable of creating connections. - private let connector: any HTTP2Connector - - /// Connection backoff configuration. - private let backoff: ConnectionBackoff - - /// The default compression algorithm to use. Can be overridden on a per-call basis. - private let defaultCompression: CompressionAlgorithm - - /// The set of enabled compression algorithms. - private let enabledCompression: CompressionAlgorithmSet - - /// The state of the load-balancer. - private let state: Mutex - - /// The ID of this load balancer. - internal let id: LoadBalancerID - - package init( - connector: any HTTP2Connector, - backoff: ConnectionBackoff, - defaultCompression: CompressionAlgorithm, - enabledCompression: CompressionAlgorithmSet - ) { - self.connector = connector - self.backoff = backoff - self.defaultCompression = defaultCompression - self.enabledCompression = enabledCompression - self.id = LoadBalancerID() - self.state = Mutex(State()) - - self.event = AsyncStream.makeStream(of: LoadBalancerEvent.self) - self.input = AsyncStream.makeStream(of: Input.self) - // The load balancer starts in the idle state. - self.event.continuation.yield(.connectivityStateChanged(.idle)) - } - - /// A stream of events which can happen to the load balancer. - package var events: AsyncStream { - self.event.stream - } - - /// Runs the load balancer, returning when it has closed. - /// - /// You can monitor events which happen on the load balancer with ``events``. - package func run() async { - await withDiscardingTaskGroup { group in - for await input in self.input.stream { - switch input { - case .updateEndpoint(let endpoint): - self.handleUpdateEndpoint(endpoint, in: &group) - case .close: - self.handleCloseInput() - } - } - } - - if Task.isCancelled { - // Finish the event stream as it's unlikely to have been finished by a regular code path. - self.event.continuation.finish() - } - } - - /// Update the addresses used by the load balancer. - /// - /// This may result in new subchannels being created and some subchannels being removed. - package func updateEndpoint(_ endpoint: Endpoint) { - self.input.continuation.yield(.updateEndpoint(endpoint)) - } - - /// Close the load balancer, and all subchannels it manages. - package func close() { - self.input.continuation.yield(.close) - } - - /// Pick a ready subchannel from the load balancer. - /// - /// - Returns: A subchannel, or `nil` if there aren't any ready subchannels. - package func pickSubchannel() -> Subchannel? { - let onPickSubchannel = self.state.withLock { $0.pickSubchannel() } - switch onPickSubchannel { - case .picked(let subchannel): - return subchannel - case .notAvailable(let subchannel): - subchannel?.connect() - return nil - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension PickFirstLoadBalancer { - private func handleUpdateEndpoint(_ endpoint: Endpoint, in group: inout DiscardingTaskGroup) { - if endpoint.addresses.isEmpty { return } - - let onUpdate = self.state.withLock { state in - state.updateEndpoint(endpoint) { endpoint, id in - Subchannel( - endpoint: endpoint, - id: id, - connector: self.connector, - backoff: self.backoff, - defaultCompression: self.defaultCompression, - enabledCompression: self.enabledCompression - ) - } - } - - switch onUpdate { - case .connect(let newSubchannel, close: let oldSubchannel): - self.runSubchannel(newSubchannel, in: &group) - oldSubchannel?.shutDown() - - case .none: - () - } - } - - private func runSubchannel( - _ subchannel: Subchannel, - in group: inout DiscardingTaskGroup - ) { - // Start running it and tell it to connect. - subchannel.connect() - group.addTask { - await subchannel.run() - } - - group.addTask { - for await event in subchannel.events { - switch event { - case .connectivityStateChanged(let state): - self.handleSubchannelConnectivityStateChange(state, id: subchannel.id) - case .goingAway: - self.handleGoAway(id: subchannel.id) - case .requiresNameResolution: - self.event.continuation.yield(.requiresNameResolution) - } - } - } - } - - private func handleSubchannelConnectivityStateChange( - _ connectivityState: ConnectivityState, - id: SubchannelID - ) { - let onUpdateState = self.state.withLock { - $0.updateSubchannelConnectivityState(connectivityState, id: id) - } - - switch onUpdateState { - case .close(let subchannel): - subchannel.shutDown() - case .closeAndPublishStateChange(let subchannel, let connectivityState): - subchannel.shutDown() - self.event.continuation.yield(.connectivityStateChanged(connectivityState)) - case .publishStateChange(let connectivityState): - self.event.continuation.yield(.connectivityStateChanged(connectivityState)) - case .closed: - self.event.continuation.finish() - self.input.continuation.finish() - case .none: - () - } - } - - private func handleGoAway(id: SubchannelID) { - self.state.withLock { state in - state.receivedGoAway(id: id) - } - } - - private func handleCloseInput() { - let onClose = self.state.withLock { $0.close() } - switch onClose { - case .closeSubchannels(let subchannel1, let subchannel2): - self.event.continuation.yield(.connectivityStateChanged(.shutdown)) - subchannel1.shutDown() - subchannel2?.shutDown() - - case .closed: - self.event.continuation.yield(.connectivityStateChanged(.shutdown)) - self.event.continuation.finish() - self.input.continuation.finish() - - case .none: - () - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension PickFirstLoadBalancer { - enum State: Sendable { - case active(Active) - case closing(Closing) - case closed - - init() { - self = .active(Active()) - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension PickFirstLoadBalancer.State { - struct Active: Sendable { - var endpoint: Endpoint? - var connectivityState: ConnectivityState - var current: Subchannel? - var next: Subchannel? - var parked: [SubchannelID: Subchannel] - var isCurrentGoingAway: Bool - - init() { - self.endpoint = nil - self.connectivityState = .idle - self.current = nil - self.next = nil - self.parked = [:] - self.isCurrentGoingAway = false - } - } - - struct Closing: Sendable { - var parked: [SubchannelID: Subchannel] - - init(from state: Active) { - self.parked = state.parked - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension PickFirstLoadBalancer.State.Active { - mutating func updateEndpoint( - _ endpoint: Endpoint, - makeSubchannel: (_ endpoint: Endpoint, _ id: SubchannelID) -> Subchannel - ) -> PickFirstLoadBalancer.State.OnUpdateEndpoint { - if self.endpoint == endpoint { return .none } - - let onUpdateEndpoint: PickFirstLoadBalancer.State.OnUpdateEndpoint - - let id = SubchannelID() - let newSubchannel = makeSubchannel(endpoint, id) - - switch (self.current, self.next) { - case (.some(let current), .none): - if self.connectivityState == .idle { - // Current subchannel is idle and we have a new endpoint, move straight to the new - // subchannel. - self.current = newSubchannel - self.parked[current.id] = current - onUpdateEndpoint = .connect(newSubchannel, close: current) - } else { - // Current subchannel is in a non-idle state, set it as the next subchannel and promote - // it when it becomes ready. - self.next = newSubchannel - onUpdateEndpoint = .connect(newSubchannel, close: nil) - } - - case (.some, .some(let next)): - // Current and next subchannel exist. Replace the next subchannel. - self.next = newSubchannel - self.parked[next.id] = next - onUpdateEndpoint = .connect(newSubchannel, close: next) - - case (.none, .none): - self.current = newSubchannel - onUpdateEndpoint = .connect(newSubchannel, close: nil) - - case (.none, .some(let next)): - self.current = newSubchannel - self.next = nil - self.parked[next.id] = next - onUpdateEndpoint = .connect(newSubchannel, close: next) - } - - return onUpdateEndpoint - } - - mutating func updateSubchannelConnectivityState( - _ connectivityState: ConnectivityState, - id: SubchannelID - ) -> (PickFirstLoadBalancer.State.OnConnectivityStateUpdate, PickFirstLoadBalancer.State) { - let onUpdate: PickFirstLoadBalancer.State.OnConnectivityStateUpdate - - if let current = self.current, current.id == id { - if connectivityState == self.connectivityState { - onUpdate = .none - } else { - self.connectivityState = connectivityState - onUpdate = .publishStateChange(connectivityState) - } - } else if let next = self.next, next.id == id { - // if it becomes ready then promote it - switch connectivityState { - case .ready: - if self.connectivityState != connectivityState { - self.connectivityState = connectivityState - - if let current = self.current { - onUpdate = .closeAndPublishStateChange(current, connectivityState) - } else { - onUpdate = .publishStateChange(connectivityState) - } - - self.current = next - self.isCurrentGoingAway = false - } else { - // No state change to publish, just roll over. - onUpdate = self.current.map { .close($0) } ?? .none - self.current = next - self.isCurrentGoingAway = false - } - - case .idle, .connecting, .transientFailure, .shutdown: - onUpdate = .none - } - - } else { - switch connectivityState { - case .idle: - if let subchannel = self.parked[id] { - onUpdate = .close(subchannel) - } else { - onUpdate = .none - } - - case .shutdown: - self.parked.removeValue(forKey: id) - onUpdate = .none - - case .connecting, .ready, .transientFailure: - onUpdate = .none - } - } - - return (onUpdate, .active(self)) - } - - mutating func receivedGoAway(id: SubchannelID) { - if let current = self.current, current.id == id { - // When receiving a GOAWAY the subchannel will ask for an address to be re-resolved and the - // connection will eventually become idle. At this point we wait: the connection remains - // in its current state. - self.isCurrentGoingAway = true - } else if let next = self.next, next.id == id { - // The next connection is going away, park it. - // connection. - self.next = nil - self.parked[next.id] = next - } - } - - mutating func close() -> (PickFirstLoadBalancer.State.OnClose, PickFirstLoadBalancer.State) { - let onClose: PickFirstLoadBalancer.State.OnClose - let nextState: PickFirstLoadBalancer.State - - if let current = self.current { - self.parked[current.id] = current - if let next = self.next { - self.parked[next.id] = next - onClose = .closeSubchannels(current, next) - } else { - onClose = .closeSubchannels(current, nil) - } - nextState = .closing(PickFirstLoadBalancer.State.Closing(from: self)) - } else { - onClose = .closed - nextState = .closed - } - - return (onClose, nextState) - } - - func pickSubchannel() -> PickFirstLoadBalancer.State.OnPickSubchannel { - let onPick: PickFirstLoadBalancer.State.OnPickSubchannel - - if let current = self.current, !self.isCurrentGoingAway { - switch self.connectivityState { - case .idle: - onPick = .notAvailable(current) - case .ready: - onPick = .picked(current) - case .connecting, .transientFailure, .shutdown: - onPick = .notAvailable(nil) - } - } else { - onPick = .notAvailable(nil) - } - - return onPick - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension PickFirstLoadBalancer.State.Closing { - mutating func updateSubchannelConnectivityState( - _ connectivityState: ConnectivityState, - id: SubchannelID - ) -> (PickFirstLoadBalancer.State.OnConnectivityStateUpdate, PickFirstLoadBalancer.State) { - let onUpdate: PickFirstLoadBalancer.State.OnConnectivityStateUpdate - let nextState: PickFirstLoadBalancer.State - - switch connectivityState { - case .idle: - if let subchannel = self.parked[id] { - onUpdate = .close(subchannel) - } else { - onUpdate = .none - } - nextState = .closing(self) - - case .shutdown: - if self.parked.removeValue(forKey: id) != nil { - if self.parked.isEmpty { - onUpdate = .closed - nextState = .closed - } else { - onUpdate = .none - nextState = .closing(self) - } - } else { - onUpdate = .none - nextState = .closing(self) - } - - case .connecting, .ready, .transientFailure: - onUpdate = .none - nextState = .closing(self) - } - - return (onUpdate, nextState) - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension PickFirstLoadBalancer.State { - enum OnUpdateEndpoint { - case connect(Subchannel, close: Subchannel?) - case none - } - - mutating func updateEndpoint( - _ endpoint: Endpoint, - makeSubchannel: (_ endpoint: Endpoint, _ id: SubchannelID) -> Subchannel - ) -> OnUpdateEndpoint { - let onUpdateEndpoint: OnUpdateEndpoint - - switch self { - case .active(var state): - onUpdateEndpoint = state.updateEndpoint(endpoint) { endpoint, id in - makeSubchannel(endpoint, id) - } - self = .active(state) - - case .closing, .closed: - onUpdateEndpoint = .none - } - - return onUpdateEndpoint - } - - enum OnConnectivityStateUpdate { - case closeAndPublishStateChange(Subchannel, ConnectivityState) - case publishStateChange(ConnectivityState) - case close(Subchannel) - case closed - case none - } - - mutating func updateSubchannelConnectivityState( - _ connectivityState: ConnectivityState, - id: SubchannelID - ) -> OnConnectivityStateUpdate { - let onUpdateState: OnConnectivityStateUpdate - - switch self { - case .active(var state): - (onUpdateState, self) = state.updateSubchannelConnectivityState(connectivityState, id: id) - case .closing(var state): - (onUpdateState, self) = state.updateSubchannelConnectivityState(connectivityState, id: id) - case .closed: - onUpdateState = .none - } - - return onUpdateState - } - - mutating func receivedGoAway(id: SubchannelID) { - switch self { - case .active(var state): - state.receivedGoAway(id: id) - self = .active(state) - case .closing, .closed: - () - } - } - - enum OnClose { - case closeSubchannels(Subchannel, Subchannel?) - case closed - case none - } - - mutating func close() -> OnClose { - let onClose: OnClose - - switch self { - case .active(var state): - (onClose, self) = state.close() - case .closing, .closed: - onClose = .none - } - - return onClose - } - - enum OnPickSubchannel { - case picked(Subchannel) - case notAvailable(Subchannel?) - } - - func pickSubchannel() -> OnPickSubchannel { - switch self { - case .active(let state): - return state.pickSubchannel() - case .closing, .closed: - return .notAvailable(nil) - } - } -} diff --git a/Sources/GRPCHTTP2Core/Client/Connection/LoadBalancers/RoundRobinLoadBalancer.swift b/Sources/GRPCHTTP2Core/Client/Connection/LoadBalancers/RoundRobinLoadBalancer.swift deleted file mode 100644 index 5c0709175..000000000 --- a/Sources/GRPCHTTP2Core/Client/Connection/LoadBalancers/RoundRobinLoadBalancer.swift +++ /dev/null @@ -1,764 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -package import GRPCCore -private import NIOConcurrencyHelpers - -/// A load-balancer which maintains to a set of subchannels and uses round-robin to pick a -/// subchannel when picking a subchannel to use. -/// -/// This load-balancer starts in an 'idle' state and begins connecting when a set of addresses is -/// provided to it with ``updateAddresses(_:)``. Repeated calls to ``updateAddresses(_:)`` will -/// update the subchannels gracefully: new subchannels will be added for new addresses and existing -/// subchannels will be removed if their addresses are no longer present. -/// -/// The state of the load-balancer is aggregated across the state of its subchannels, changes in -/// the aggregate state are reported up via ``events``. -/// -/// You must call ``close()`` on the load-balancer when it's no longer required. This will move -/// it to the ``ConnectivityState/shutdown`` state: existing RPCs may continue but all subsequent -/// calls to ``makeStream(descriptor:options:)`` will fail. -/// -/// To use this load-balancer you must run it in a task: -/// -/// ```swift -/// await withDiscardingTaskGroup { group in -/// // Run the load-balancer -/// group.addTask { await roundRobin.run() } -/// -/// // Update its address list -/// let endpoints: [Endpoint] = [ -/// Endpoint(addresses: [.ipv4(host: "127.0.0.1", port: 1001)]), -/// Endpoint(addresses: [.ipv4(host: "127.0.0.1", port: 1002)]), -/// Endpoint(addresses: [.ipv4(host: "127.0.0.1", port: 1003)]) -/// ] -/// roundRobin.updateAddresses(endpoints) -/// -/// // Consume state update events -/// for await event in roundRobin.events { -/// switch event { -/// case .connectivityStateChanged(.ready): -/// // ... -/// default: -/// // ... -/// } -/// } -/// } -/// ``` -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -package final class RoundRobinLoadBalancer: Sendable { - enum Input: Sendable, Hashable { - /// Update the addresses used by the load balancer to the following endpoints. - case updateAddresses([Endpoint]) - /// Close the load balancer. - case close - } - - /// A key for an endpoint which identifies it uniquely, regardless of the ordering of addresses. - private struct EndpointKey: Hashable, Sendable, CustomStringConvertible { - /// Opaque data. - private let opaque: [String] - - /// The endpoint this key is for. - let endpoint: Endpoint - - init(_ endpoint: Endpoint) { - self.endpoint = endpoint - self.opaque = endpoint.addresses.map { String(describing: $0) }.sorted() - } - - var description: String { - String(describing: self.endpoint.addresses) - } - - func hash(into hasher: inout Hasher) { - hasher.combine(self.opaque) - } - - static func == (lhs: Self, rhs: Self) -> Bool { - lhs.opaque == rhs.opaque - } - } - - /// Events which can happen to the load balancer. - private let event: - ( - stream: AsyncStream, - continuation: AsyncStream.Continuation - ) - - /// Inputs which this load balancer should react to. - private let input: (stream: AsyncStream, continuation: AsyncStream.Continuation) - - // Uses NIOLockedValueBox to workaround: https://github.com/swiftlang/swift/issues/76007 - /// The state of the load balancer. - private let state: NIOLockedValueBox - - /// A connector, capable of creating connections. - private let connector: any HTTP2Connector - - /// Connection backoff configuration. - private let backoff: ConnectionBackoff - - /// The default compression algorithm to use. Can be overridden on a per-call basis. - private let defaultCompression: CompressionAlgorithm - - /// The set of enabled compression algorithms. - private let enabledCompression: CompressionAlgorithmSet - - /// The ID of this load balancer. - internal let id: LoadBalancerID - - package init( - connector: any HTTP2Connector, - backoff: ConnectionBackoff, - defaultCompression: CompressionAlgorithm, - enabledCompression: CompressionAlgorithmSet - ) { - self.connector = connector - self.backoff = backoff - self.defaultCompression = defaultCompression - self.enabledCompression = enabledCompression - self.id = LoadBalancerID() - - self.event = AsyncStream.makeStream(of: LoadBalancerEvent.self) - self.input = AsyncStream.makeStream(of: Input.self) - self.state = NIOLockedValueBox(.active(State.Active())) - - // The load balancer starts in the idle state. - self.event.continuation.yield(.connectivityStateChanged(.idle)) - } - - /// A stream of events which can happen to the load balancer. - package var events: AsyncStream { - self.event.stream - } - - /// Runs the load balancer, returning when it has closed. - /// - /// You can monitor events which happen on the load balancer with ``events``. - package func run() async { - await withDiscardingTaskGroup { group in - for await input in self.input.stream { - switch input { - case .updateAddresses(let addresses): - self.handleUpdateAddresses(addresses, in: &group) - case .close: - self.handleCloseInput() - } - } - } - - if Task.isCancelled { - // Finish the event stream as it's unlikely to have been finished by a regular code path. - self.event.continuation.finish() - } - } - - /// Update the addresses used by the load balancer. - /// - /// This may result in new subchannels being created and some subchannels being removed. - package func updateAddresses(_ endpoints: [Endpoint]) { - self.input.continuation.yield(.updateAddresses(endpoints)) - } - - /// Close the load balancer, and all subchannels it manages. - package func close() { - self.input.continuation.yield(.close) - } - - /// Pick a ready subchannel from the load balancer. - /// - /// - Returns: A subchannel, or `nil` if there aren't any ready subchannels. - package func pickSubchannel() -> Subchannel? { - switch self.state.withLockedValue({ $0.pickSubchannel() }) { - case .picked(let subchannel): - return subchannel - - case .notAvailable(let subchannels): - // Tell the subchannels to start connecting. - for subchannel in subchannels { - subchannel.connect() - } - return nil - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension RoundRobinLoadBalancer { - /// Handles an update in endpoints. - /// - /// The load-balancer will diff the set of endpoints with the existing set of endpoints: - /// - endpoints which are new will have subchannels created for them, - /// - endpoints which existed previously but are not present in `endpoints` are closed, - /// - endpoints which existed previously and are still present in `endpoints` are untouched. - /// - /// This process is gradual: the load-balancer won't remove an old endpoint until a subchannel - /// for a corresponding new subchannel becomes ready. - /// - /// - Parameters: - /// - endpoints: Endpoints which should have subchannels. Must not be empty. - /// - group: The group which should manage and run new subchannels. - private func handleUpdateAddresses(_ endpoints: [Endpoint], in group: inout DiscardingTaskGroup) { - if endpoints.isEmpty { return } - - // Compute the keys for each endpoint. - let newEndpoints = Set(endpoints.map { EndpointKey($0) }) - - let (added, removed, newState) = self.state.withLockedValue { state in - state.updateSubchannels(newEndpoints: newEndpoints) { endpoint, id in - Subchannel( - endpoint: endpoint, - id: id, - connector: self.connector, - backoff: self.backoff, - defaultCompression: self.defaultCompression, - enabledCompression: self.enabledCompression - ) - } - } - - // Publish the new connectivity state. - if let newState = newState { - self.event.continuation.yield(.connectivityStateChanged(newState)) - } - - // Run each of the new subchannels. - for subchannel in added { - let key = EndpointKey(subchannel.endpoint) - self.runSubchannel(subchannel, forKey: key, in: &group) - } - - // Old subchannels are removed when new subchannels become ready. Excess subchannels are only - // present if there are more to remove than to add. These are the excess subchannels which - // are closed now. - for subchannel in removed { - subchannel.shutDown() - } - } - - private func runSubchannel( - _ subchannel: Subchannel, - forKey key: EndpointKey, - in group: inout DiscardingTaskGroup - ) { - // Start running it and tell it to connect. - subchannel.connect() - group.addTask { - await subchannel.run() - } - - group.addTask { - for await event in subchannel.events { - switch event { - case .connectivityStateChanged(let state): - self.handleSubchannelConnectivityStateChange(state, key: key) - case .goingAway: - self.handleSubchannelGoingAway(key: key) - case .requiresNameResolution: - self.event.continuation.yield(.requiresNameResolution) - } - } - } - } - - private func handleSubchannelConnectivityStateChange( - _ connectivityState: ConnectivityState, - key: EndpointKey - ) { - let onChange = self.state.withLockedValue { state in - state.updateSubchannelConnectivityState(connectivityState, key: key) - } - - switch onChange { - case .publishStateChange(let aggregateState): - self.event.continuation.yield(.connectivityStateChanged(aggregateState)) - - case .closeAndPublishStateChange(let subchannel, let aggregateState): - self.event.continuation.yield(.connectivityStateChanged(aggregateState)) - subchannel.shutDown() - - case .close(let subchannel): - subchannel.shutDown() - - case .closed: - // All subchannels are closed; finish the streams so the run loop exits. - self.event.continuation.finish() - self.input.continuation.finish() - - case .none: - () - } - } - - private func handleSubchannelGoingAway(key: EndpointKey) { - switch self.state.withLockedValue({ $0.parkSubchannel(withKey: key) }) { - case .closeAndUpdateState(let subchannel, let connectivityState): - subchannel.shutDown() - if let connectivityState = connectivityState { - self.event.continuation.yield(.connectivityStateChanged(connectivityState)) - } - case .none: - () - } - } - - private func handleCloseInput() { - switch self.state.withLockedValue({ $0.close() }) { - case .closeSubchannels(let subchannels): - // Publish a new shutdown state, this LB is no longer usable for new RPCs. - self.event.continuation.yield(.connectivityStateChanged(.shutdown)) - - // Close the subchannels. - for subchannel in subchannels { - subchannel.shutDown() - } - - case .closed: - // No subchannels to close. - self.event.continuation.yield(.connectivityStateChanged(.shutdown)) - self.event.continuation.finish() - self.input.continuation.finish() - - case .none: - () - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension RoundRobinLoadBalancer { - private enum State { - case active(Active) - case closing(Closing) - case closed - - struct Active { - private(set) var aggregateConnectivityState: ConnectivityState - private var picker: Picker? - - var endpoints: [Endpoint] - var subchannels: [EndpointKey: SubchannelState] - var parkedSubchannels: [EndpointKey: Subchannel] - - init() { - self.endpoints = [] - self.subchannels = [:] - self.parkedSubchannels = [:] - self.aggregateConnectivityState = .idle - self.picker = nil - } - - mutating func updateConnectivityState( - _ state: ConnectivityState, - key: EndpointKey - ) -> OnSubchannelConnectivityStateUpdate { - if let changed = self.subchannels[key]?.updateState(state) { - guard changed else { return .none } - - let subchannelToClose: Subchannel? - - switch state { - case .ready: - if let index = self.subchannels.firstIndex(where: { $0.value.markedForRemoval }) { - let (key, subchannelState) = self.subchannels.remove(at: index) - self.parkedSubchannels[key] = subchannelState.subchannel - subchannelToClose = subchannelState.subchannel - } else { - subchannelToClose = nil - } - - case .idle, .connecting, .transientFailure, .shutdown: - subchannelToClose = nil - } - - let aggregateState = self.refreshPickerAndAggregateState() - - switch (subchannelToClose, aggregateState) { - case (.some(let subchannel), .some(let state)): - return .closeAndPublishStateChange(subchannel, state) - case (.some(let subchannel), .none): - return .close(subchannel) - case (.none, .some(let state)): - return .publishStateChange(state) - case (.none, .none): - return .none - } - } else { - switch state { - case .idle: - // The subchannel can be parked before it's shutdown. If there are no active RPCs then - // it will enter the idle state instead. If that happens, close it. - if let parked = self.parkedSubchannels[key] { - return .close(parked) - } else { - return .none - } - case .shutdown: - self.parkedSubchannels.removeValue(forKey: key) - case .connecting, .ready, .transientFailure: - () - } - - return .none - } - } - - mutating func refreshPickerAndAggregateState() -> ConnectivityState? { - let ready = self.subchannels.values.compactMap { $0.state == .ready ? $0.subchannel : nil } - self.picker = Picker(subchannels: ready) - - let aggregate = ConnectivityState.aggregate(self.subchannels.values.map { $0.state }) - if aggregate == self.aggregateConnectivityState { - return nil - } else { - self.aggregateConnectivityState = aggregate - return aggregate - } - } - - mutating func pick() -> Subchannel? { - self.picker?.pick() - } - - mutating func markForRemoval( - _ keys: some Sequence, - numberToRemoveNow: Int - ) -> [Subchannel] { - var numberToRemoveNow = numberToRemoveNow - var keyIterator = keys.makeIterator() - var subchannelsToClose = [Subchannel]() - - while numberToRemoveNow > 0, let key = keyIterator.next() { - if let subchannelState = self.subchannels.removeValue(forKey: key) { - numberToRemoveNow -= 1 - self.parkedSubchannels[key] = subchannelState.subchannel - subchannelsToClose.append(subchannelState.subchannel) - } - } - - while let key = keyIterator.next() { - self.subchannels[key]?.markForRemoval() - } - - return subchannelsToClose - } - - mutating func registerSubchannels( - withKeys keys: some Sequence, - _ makeSubchannel: (_ endpoint: Endpoint, _ id: SubchannelID) -> Subchannel - ) -> [Subchannel] { - var subchannels = [Subchannel]() - - for key in keys { - let subchannel = makeSubchannel(key.endpoint, SubchannelID()) - subchannels.append(subchannel) - self.subchannels[key] = SubchannelState(subchannel: subchannel) - } - - return subchannels - } - } - - struct Closing { - enum Reason: Sendable, Hashable { - case goAway - case user - } - - var reason: Reason - var parkedSubchannels: [EndpointKey: Subchannel] - - mutating func updateConnectivityState( - _ state: ConnectivityState, - key: EndpointKey - ) -> (OnSubchannelConnectivityStateUpdate, RoundRobinLoadBalancer.State) { - let result: OnSubchannelConnectivityStateUpdate - let nextState: RoundRobinLoadBalancer.State - - switch state { - case .idle: - if let parked = self.parkedSubchannels[key] { - result = .close(parked) - } else { - result = .none - } - nextState = .closing(self) - - case .shutdown: - self.parkedSubchannels.removeValue(forKey: key) - if self.parkedSubchannels.isEmpty { - nextState = .closed - result = .closed - } else { - nextState = .closing(self) - result = .none - } - - case .connecting, .ready, .transientFailure: - result = .none - nextState = .closing(self) - } - - return (result, nextState) - } - } - - struct SubchannelState { - var subchannel: Subchannel - var state: ConnectivityState - var markedForRemoval: Bool - - init(subchannel: Subchannel) { - self.subchannel = subchannel - self.state = .idle - self.markedForRemoval = false - } - - mutating func updateState(_ newState: ConnectivityState) -> Bool { - // The transition from transient failure to connecting is ignored. - // - // See: https://github.com/grpc/grpc/blob/master/doc/load-balancing.md - if self.state == .transientFailure, newState == .connecting { - return false - } - - let oldState = self.state - self.state = newState - return oldState != newState - } - - mutating func markForRemoval() { - self.markedForRemoval = true - } - } - - struct Picker { - private var subchannels: [Subchannel] - private var index: Int - - init?(subchannels: [Subchannel]) { - if subchannels.isEmpty { return nil } - - self.subchannels = subchannels - self.index = (0 ..< subchannels.count).randomElement()! - } - - mutating func pick() -> Subchannel { - defer { - self.index = (self.index + 1) % self.subchannels.count - } - return self.subchannels[self.index] - } - } - - mutating func updateSubchannels( - newEndpoints: Set, - makeSubchannel: (_ endpoint: Endpoint, _ id: SubchannelID) -> Subchannel - ) -> (run: [Subchannel], close: [Subchannel], newState: ConnectivityState?) { - switch self { - case .active(var state): - let existingEndpoints = Set(state.subchannels.keys) - let keysToAdd = newEndpoints.subtracting(existingEndpoints) - let keysToRemove = existingEndpoints.subtracting(newEndpoints) - - if keysToRemove.isEmpty && keysToAdd.isEmpty { - // Nothing to do. - return (run: [], close: [], newState: nil) - } - - // The load balancer should keep subchannels to remove in service until new subchannels - // can replace each of them so that requests can continue to be served. - // - // If there are more keys to remove than to add, remove some now. - let numberToRemoveNow = max(keysToRemove.count - keysToAdd.count, 0) - - let removed = state.markForRemoval(keysToRemove, numberToRemoveNow: numberToRemoveNow) - let added = state.registerSubchannels(withKeys: keysToAdd, makeSubchannel) - - let newState = state.refreshPickerAndAggregateState() - self = .active(state) - return (run: added, close: removed, newState: newState) - - case .closing, .closed: - // Nothing to do. - return (run: [], close: [], newState: nil) - } - - } - - enum OnParkChannel { - case closeAndUpdateState(Subchannel, ConnectivityState?) - case none - } - - mutating func parkSubchannel(withKey key: EndpointKey) -> OnParkChannel { - switch self { - case .active(var state): - guard let subchannelState = state.subchannels.removeValue(forKey: key) else { - return .none - } - - // Parking the subchannel may invalidate the picker and the aggregate state, refresh both. - state.parkedSubchannels[key] = subchannelState.subchannel - let newState = state.refreshPickerAndAggregateState() - self = .active(state) - return .closeAndUpdateState(subchannelState.subchannel, newState) - - case .closing, .closed: - return .none - } - } - - mutating func registerSubchannels( - withKeys keys: some Sequence, - _ makeSubchannel: (Endpoint) -> Subchannel - ) -> [Subchannel] { - switch self { - case .active(var state): - var subchannels = [Subchannel]() - - for key in keys { - let subchannel = makeSubchannel(key.endpoint) - subchannels.append(subchannel) - state.subchannels[key] = SubchannelState(subchannel: subchannel) - } - - self = .active(state) - return subchannels - - case .closing, .closed: - return [] - } - } - - enum OnSubchannelConnectivityStateUpdate { - case closeAndPublishStateChange(Subchannel, ConnectivityState) - case publishStateChange(ConnectivityState) - case close(Subchannel) - case closed - case none - } - - mutating func updateSubchannelConnectivityState( - _ connectivityState: ConnectivityState, - key: EndpointKey - ) -> OnSubchannelConnectivityStateUpdate { - switch self { - case .active(var state): - let result = state.updateConnectivityState(connectivityState, key: key) - self = .active(state) - return result - - case .closing(var state): - let (result, nextState) = state.updateConnectivityState(connectivityState, key: key) - self = nextState - return result - - case .closed: - return .none - } - } - - enum OnClose { - case closeSubchannels([Subchannel]) - case closed - case none - } - - mutating func close() -> OnClose { - switch self { - case .active(var active): - var subchannelsToClose = [Subchannel]() - - for (id, subchannelState) in active.subchannels { - subchannelsToClose.append(subchannelState.subchannel) - active.parkedSubchannels[id] = subchannelState.subchannel - } - - if subchannelsToClose.isEmpty { - self = .closed - return .closed - } else { - self = .closing(Closing(reason: .user, parkedSubchannels: active.parkedSubchannels)) - return .closeSubchannels(subchannelsToClose) - } - - case .closing, .closed: - return .none - } - } - - enum OnPickSubchannel { - case picked(Subchannel) - case notAvailable([Subchannel]) - } - - mutating func pickSubchannel() -> OnPickSubchannel { - let onMakeStream: OnPickSubchannel - - switch self { - case .active(var active): - if let subchannel = active.pick() { - onMakeStream = .picked(subchannel) - } else { - switch active.aggregateConnectivityState { - case .idle: - onMakeStream = .notAvailable(active.subchannels.values.map { $0.subchannel }) - case .connecting, .ready, .transientFailure, .shutdown: - onMakeStream = .notAvailable([]) - } - } - self = .active(active) - - case .closing, .closed: - onMakeStream = .notAvailable([]) - } - - return onMakeStream - } - } -} - -extension ConnectivityState { - static func aggregate(_ states: some Collection) -> ConnectivityState { - // See https://github.com/grpc/grpc/blob/master/doc/load-balancing.md - - // If any one subchannel is in READY state, the channel's state is READY. - if states.contains(where: { $0 == .ready }) { - return .ready - } - - // Otherwise, if there is any subchannel in state CONNECTING, the channel's state is CONNECTING. - if states.contains(where: { $0 == .connecting }) { - return .connecting - } - - // Otherwise, if there is any subchannel in state IDLE, the channel's state is IDLE. - if states.contains(where: { $0 == .idle }) { - return .idle - } - - // Otherwise, if all subchannels are in state TRANSIENT_FAILURE, the channel's state - // is TRANSIENT_FAILURE. - if states.allSatisfy({ $0 == .transientFailure }) { - return .transientFailure - } - - return .shutdown - } -} diff --git a/Sources/GRPCHTTP2Core/Client/Connection/LoadBalancers/Subchannel.swift b/Sources/GRPCHTTP2Core/Client/Connection/LoadBalancers/Subchannel.swift deleted file mode 100644 index 64f53e305..000000000 --- a/Sources/GRPCHTTP2Core/Client/Connection/LoadBalancers/Subchannel.swift +++ /dev/null @@ -1,680 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -package import GRPCCore -private import Synchronization - -/// A ``Subchannel`` provides communication to a single ``Endpoint``. -/// -/// Each ``Subchannel`` starts in an 'idle' state where it isn't attempting to connect to an -/// endpoint. You can tell it to start connecting by calling ``connect()`` and you can listen -/// to connectivity state changes by consuming the ``events`` sequence. -/// -/// You must call ``shutDown()`` on the ``Subchannel`` when it's no longer required. This will move -/// it to the ``ConnectivityState/shutdown`` state: existing RPCs may continue but all subsequent -/// calls to ``makeStream(descriptor:options:)`` will fail. -/// -/// To use the ``Subchannel`` you must run it in a task: -/// -/// ```swift -/// await withTaskGroup(of: Void.self) { group in -/// group.addTask { await subchannel.run() } -/// -/// for await event in subchannel.events { -/// switch event { -/// case .connectivityStateChanged(.ready): -/// // ... -/// default: -/// // ... -/// } -/// } -/// } -/// ``` -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -package final class Subchannel: Sendable { - package enum Event: Sendable, Hashable { - /// The connection received a GOAWAY and will close soon. No new streams - /// should be opened on this connection. - case goingAway - /// The connectivity state of the subchannel changed. - case connectivityStateChanged(ConnectivityState) - /// The subchannel requests that the load balancer re-resolves names. - case requiresNameResolution - } - - private enum Input: Sendable { - /// Request that the connection starts connecting. - case connect - /// A backoff period has ended. - case backedOff - /// Shuts down the connection, if possible. - case shutDown - /// Handle the event from the underlying connection object. - case handleConnectionEvent(Connection.Event) - } - - /// Events which can happen to the subchannel. - private let event: (stream: AsyncStream, continuation: AsyncStream.Continuation) - - /// Inputs which this subchannel should react to. - private let input: (stream: AsyncStream, continuation: AsyncStream.Continuation) - - /// The state of the subchannel. - private let state: Mutex - - /// The endpoint this subchannel is targeting. - let endpoint: Endpoint - - /// The ID of the subchannel. - package let id: SubchannelID - - /// A factory for connections. - private let connector: any HTTP2Connector - - /// The connection backoff configuration used by the subchannel when establishing a connection. - private let backoff: ConnectionBackoff - - /// The default compression algorithm used for requests. - private let defaultCompression: CompressionAlgorithm - - /// The set of enabled compression algorithms. - private let enabledCompression: CompressionAlgorithmSet - - package init( - endpoint: Endpoint, - id: SubchannelID, - connector: any HTTP2Connector, - backoff: ConnectionBackoff, - defaultCompression: CompressionAlgorithm, - enabledCompression: CompressionAlgorithmSet - ) { - assert(!endpoint.addresses.isEmpty, "endpoint.addresses mustn't be empty") - - self.state = Mutex(.notConnected(.initial)) - self.endpoint = endpoint - self.id = id - self.connector = connector - self.backoff = backoff - self.defaultCompression = defaultCompression - self.enabledCompression = enabledCompression - self.event = AsyncStream.makeStream(of: Event.self) - self.input = AsyncStream.makeStream(of: Input.self) - // Subchannel always starts in the idle state. - self.event.continuation.yield(.connectivityStateChanged(.idle)) - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension Subchannel { - /// A stream of events which can happen to the subchannel. - package var events: AsyncStream { - self.event.stream - } - - /// Run the subchannel. - /// - /// Running the subchannel will attempt to maintain a connection to a remote endpoint. At times - /// the connection may be idle but it will reconnect on-demand when a stream is requested. If - /// connect attempts fail then the subchannel may progressively spend longer in a transient - /// failure state. - /// - /// Events and state changes can be observed via the ``events`` stream. - package func run() async { - await withDiscardingTaskGroup { group in - for await input in self.input.stream { - switch input { - case .connect: - self.handleConnectInput(in: &group) - case .backedOff: - self.handleBackedOffInput(in: &group) - case .shutDown: - self.handleShutDownInput(in: &group) - case .handleConnectionEvent(let event): - self.handleConnectionEvent(event, in: &group) - } - } - } - - // Once the task group is done, the event stream must also be finished. In normal operation - // this is handled via other paths. For cancellation it must be finished explicitly. - if Task.isCancelled { - self.event.continuation.finish() - } - } - - /// Initiate a connection attempt, if possible. - package func connect() { - self.input.continuation.yield(.connect) - } - - /// Initiates graceful shutdown, if possible. - package func shutDown() { - self.input.continuation.yield(.shutDown) - } - - /// Make a stream using the subchannel if it's ready. - /// - /// - Parameter descriptor: A descriptor of the method to create a stream for. - /// - Returns: The open stream. - package func makeStream( - descriptor: MethodDescriptor, - options: CallOptions - ) async throws -> Connection.Stream { - let connection: Connection? = self.state.withLock { state in - switch state { - case .notConnected, .connecting, .goingAway, .shuttingDown, .shutDown: - return nil - case .connected(let connected): - return connected.connection - } - } - - guard let connection = connection else { - throw RPCError(code: .unavailable, message: "subchannel isn't ready") - } - - return try await connection.makeStream(descriptor: descriptor, options: options) - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension Subchannel { - private func handleConnectInput(in group: inout DiscardingTaskGroup) { - let connection = self.state.withLock { state in - state.makeConnection( - to: self.endpoint.addresses, - using: self.connector, - backoff: self.backoff, - defaultCompression: self.defaultCompression, - enabledCompression: self.enabledCompression - ) - } - - guard let connection = connection else { - // Not in a state to start a connection. - return - } - - // About to start connecting a new connection; emit a state change event. - self.event.continuation.yield(.connectivityStateChanged(.connecting)) - self.runConnection(connection, in: &group) - } - - private func handleBackedOffInput(in group: inout DiscardingTaskGroup) { - switch self.state.withLock({ $0.backedOff() }) { - case .none: - () - - case .finish: - self.event.continuation.finish() - self.input.continuation.finish() - - case .connect(let connection): - // About to start connecting, emit a state change event. - self.event.continuation.yield(.connectivityStateChanged(.connecting)) - self.runConnection(connection, in: &group) - } - } - - private func handleShutDownInput(in group: inout DiscardingTaskGroup) { - switch self.state.withLock({ $0.shutDown() }) { - case .none: - () - - case .emitShutdown: - // Connection closed because the load balancer asked it to, so notify the load balancer. - self.event.continuation.yield(.connectivityStateChanged(.shutdown)) - - case .emitShutdownAndClose(let connection): - // Connection closed because the load balancer asked it to, so notify the load balancer. - self.event.continuation.yield(.connectivityStateChanged(.shutdown)) - connection.close() - - case .emitShutdownAndFinish: - // Connection closed because the load balancer asked it to, so notify the load balancer. - self.event.continuation.yield(.connectivityStateChanged(.shutdown)) - // At this point there are no more events: close the event streams. - self.event.continuation.finish() - self.input.continuation.finish() - } - } - - private func handleConnectionEvent( - _ event: Connection.Event, - in group: inout DiscardingTaskGroup - ) { - switch event { - case .connectSucceeded: - self.handleConnectSucceededEvent() - case .connectFailed: - self.handleConnectFailedEvent(in: &group) - case .goingAway: - self.handleGoingAwayEvent() - case .closed(let reason): - self.handleConnectionClosedEvent(reason, in: &group) - } - } - - private func handleConnectSucceededEvent() { - switch self.state.withLock({ $0.connectSucceeded() }) { - case .updateStateToReady: - // Emit a connectivity state change: the load balancer can now use this subchannel. - self.event.continuation.yield(.connectivityStateChanged(.ready)) - - case .finishAndClose(let connection): - self.event.continuation.yield(.connectivityStateChanged(.shutdown)) - self.event.continuation.finish() - self.input.continuation.finish() - connection.close() - - case .none: - () - } - } - - private func handleConnectFailedEvent(in group: inout DiscardingTaskGroup) { - let onConnectFailed = self.state.withLock { $0.connectFailed(connector: self.connector) } - switch onConnectFailed { - case .connect(let connection): - // Try the next address. - self.runConnection(connection, in: &group) - - case .backoff(let duration): - // All addresses have been tried, backoff for some time. - self.event.continuation.yield(.connectivityStateChanged(.transientFailure)) - group.addTask { - do { - try await Task.sleep(for: duration) - self.input.continuation.yield(.backedOff) - } catch { - // Can only be a cancellation error, swallow it. No further connection attempts will be - // made. - () - } - } - - case .finish: - self.event.continuation.finish() - self.input.continuation.finish() - - case .none: - () - } - } - - private func handleGoingAwayEvent() { - let isGoingAway = self.state.withLock { $0.goingAway() } - guard isGoingAway else { return } - - // Notify the load balancer that the subchannel is going away to stop it from being used. - self.event.continuation.yield(.goingAway) - // A GOAWAY also means that the load balancer should re-resolve as the available servers - // may have changed. - self.event.continuation.yield(.requiresNameResolution) - } - - private func handleConnectionClosedEvent( - _ reason: Connection.CloseReason, - in group: inout DiscardingTaskGroup - ) { - switch self.state.withLock({ $0.closed(reason: reason) }) { - case .nothing: - () - - case .emitIdle: - self.event.continuation.yield(.connectivityStateChanged(.idle)) - - case .emitTransientFailureAndReconnect: - // Unclean closes trigger a transient failure state change and a name resolution. - self.event.continuation.yield(.connectivityStateChanged(.transientFailure)) - self.event.continuation.yield(.requiresNameResolution) - // Attempt to reconnect. - self.handleConnectInput(in: &group) - - case .finish(let emitShutdown): - if emitShutdown { - self.event.continuation.yield(.connectivityStateChanged(.shutdown)) - } - - // At this point there are no more events: close the event streams. - self.event.continuation.finish() - self.input.continuation.finish() - } - } - - private func runConnection(_ connection: Connection, in group: inout DiscardingTaskGroup) { - group.addTask { - await connection.run() - } - - group.addTask { - for await event in connection.events { - self.input.continuation.yield(.handleConnectionEvent(event)) - } - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension Subchannel { - /// ┌───────────────┐ - /// ┌───────▶│ NOT CONNECTED │───────────shutDown─────────────┐ - /// │ └───────────────┘ │ - /// │ │ │ - /// │ connFailed──┤connect │ - /// │ /backedOff │ │ - /// │ │ ▼ │ - /// │ │ ┌───────────────┐ │ - /// │ └──│ CONNECTING │──────┐ │ - /// │ └───────────────┘ │ │ - /// │ │ │ │ - /// closed connSucceeded │ │ - /// │ │ │ │ - /// │ ▼ │ │ - /// │ ┌───────────────┐ │ ┌───────────────┐ │ - /// │ │ CONNECTED │──shutDown──▶│ SHUTTING DOWN │ │ - /// │ └───────────────┘ │ └───────────────┘ │ - /// │ │ │ │ │ - /// │ goAway │ closed │ - /// │ │ │ │ │ - /// │ ▼ │ ▼ │ - /// │ ┌───────────────┐ │ ┌───────────────┐ │ - /// └────────│ GOING AWAY │──────┘ │ SHUT DOWN │◀─┘ - /// └───────────────┘ └───────────────┘ - private enum State { - /// Not connected and not actively connecting. - case notConnected(NotConnected) - /// A connection attempt is in-progress. - case connecting(Connecting) - /// A connection has been established. - case connected(Connected) - /// The subchannel is going away. It may return to the 'notConnected' state when the underlying - /// connection has closed. - case goingAway(GoingAway) - /// The subchannel is shutting down, it will enter the 'shutDown' state when closed, it may not - /// enter any other state. - case shuttingDown(ShuttingDown) - /// The subchannel is shutdown, this is a terminal state. - case shutDown(ShutDown) - - struct NotConnected { - private init() {} - static let initial = NotConnected() - init(from state: Connected) {} - init(from state: GoingAway) {} - } - - struct Connecting { - var connection: Connection - let addresses: [SocketAddress] - var addressIterator: Array.Iterator - var backoff: ConnectionBackoff.Iterator - } - - struct Connected { - var connection: Connection - - init(from state: Connecting) { - self.connection = state.connection - } - } - - struct GoingAway { - var connection: Connection - - init(from state: Connecting) { - self.connection = state.connection - } - - init(from state: Connected) { - self.connection = state.connection - } - } - - struct ShuttingDown { - var connection: Connection - - init(from state: Connecting) { - self.connection = state.connection - } - - init(from state: Connected) { - self.connection = state.connection - } - - init(from state: GoingAway) { - self.connection = state.connection - } - } - - struct ShutDown { - init(from state: ShuttingDown) {} - init(from state: GoingAway) {} - init(from state: NotConnected) {} - } - - mutating func makeConnection( - to addresses: [SocketAddress], - using connector: any HTTP2Connector, - backoff: ConnectionBackoff, - defaultCompression: CompressionAlgorithm, - enabledCompression: CompressionAlgorithmSet - ) -> Connection? { - switch self { - case .notConnected: - var iterator = addresses.makeIterator() - let address = iterator.next()! // addresses must not be empty. - - let connection = Connection( - address: address, - http2Connector: connector, - defaultCompression: defaultCompression, - enabledCompression: enabledCompression - ) - - let connecting = State.Connecting( - connection: connection, - addresses: addresses, - addressIterator: iterator, - backoff: backoff.makeIterator() - ) - - self = .connecting(connecting) - return connection - - case .connecting, .connected, .goingAway, .shuttingDown, .shutDown: - return nil - } - } - - enum OnClose { - case none - case emitShutdownAndFinish - case emitShutdownAndClose(Connection) - case emitShutdown - } - - mutating func shutDown() -> OnClose { - let onShutDown: OnClose - - switch self { - case .notConnected(let state): - self = .shutDown(ShutDown(from: state)) - onShutDown = .emitShutdownAndFinish - - case .connecting(let state): - // Only emit the shutdown; there's no connection to close yet. - self = .shuttingDown(ShuttingDown(from: state)) - onShutDown = .emitShutdown - - case .connected(let state): - self = .shuttingDown(ShuttingDown(from: state)) - onShutDown = .emitShutdownAndClose(state.connection) - - case .goingAway(let state): - self = .shuttingDown(ShuttingDown(from: state)) - onShutDown = .emitShutdown - - case .shuttingDown, .shutDown: - onShutDown = .none - } - - return onShutDown - } - - enum OnConnectSucceeded { - case updateStateToReady - case finishAndClose(Connection) - case none - } - - mutating func connectSucceeded() -> OnConnectSucceeded { - switch self { - case .connecting(let state): - self = .connected(Connected(from: state)) - return .updateStateToReady - - case .shuttingDown(let state): - self = .shutDown(ShutDown(from: state)) - return .finishAndClose(state.connection) - - case .notConnected, .connected, .goingAway, .shutDown: - return .none - } - } - - enum OnConnectFailed { - case none - case finish - case connect(Connection) - case backoff(Duration) - } - - mutating func connectFailed(connector: any HTTP2Connector) -> OnConnectFailed { - let onConnectFailed: OnConnectFailed - - switch self { - case .connecting(var state): - if let address = state.addressIterator.next() { - state.connection = Connection( - address: address, - http2Connector: connector, - defaultCompression: .none, - enabledCompression: .all - ) - self = .connecting(state) - onConnectFailed = .connect(state.connection) - } else { - state.addressIterator = state.addresses.makeIterator() - let address = state.addressIterator.next()! - state.connection = Connection( - address: address, - http2Connector: connector, - defaultCompression: .none, - enabledCompression: .all - ) - let backoff = state.backoff.next() - self = .connecting(state) - onConnectFailed = .backoff(backoff) - } - - case .shuttingDown(let state): - self = .shutDown(ShutDown(from: state)) - onConnectFailed = .finish - - case .notConnected, .connected, .goingAway, .shutDown: - onConnectFailed = .none - } - - return onConnectFailed - } - - enum OnBackedOff { - case none - case connect(Connection) - case finish - } - - mutating func backedOff() -> OnBackedOff { - switch self { - case .connecting(let state): - self = .connecting(state) - return .connect(state.connection) - - case .shuttingDown(let state): - self = .shutDown(ShutDown(from: state)) - return .finish - - case .notConnected, .connected, .goingAway, .shutDown: - return .none - } - } - - mutating func goingAway() -> Bool { - switch self { - case .connected(let state): - self = .goingAway(GoingAway(from: state)) - return true - case .notConnected, .goingAway, .connecting, .shuttingDown, .shutDown: - return false - } - } - - enum OnClosed { - case nothing - case emitIdle - case emitTransientFailureAndReconnect - case finish(emitShutdown: Bool) - } - - mutating func closed(reason: Connection.CloseReason) -> OnClosed { - let onClosed: OnClosed - - switch self { - case .connected(let state): - switch reason { - case .idleTimeout, .remote, .error(_, wasIdle: true): - self = .notConnected(NotConnected(from: state)) - onClosed = .emitIdle - - case .keepaliveTimeout, .error(_, wasIdle: false): - self = .notConnected(NotConnected(from: state)) - onClosed = .emitTransientFailureAndReconnect - - case .initiatedLocally: - // Should be in the 'shuttingDown' state. - assertionFailure("Invalid state") - let shuttingDown = State.ShuttingDown(from: state) - self = .shutDown(ShutDown(from: shuttingDown)) - onClosed = .finish(emitShutdown: true) - } - - case .goingAway(let state): - self = .notConnected(NotConnected(from: state)) - onClosed = .emitIdle - - case .shuttingDown(let state): - self = .shutDown(ShutDown(from: state)) - return .finish(emitShutdown: false) - - case .notConnected, .connecting, .shutDown: - onClosed = .nothing - } - - return onClosed - } - } -} diff --git a/Sources/GRPCHTTP2Core/Client/Connection/RequestQueue.swift b/Sources/GRPCHTTP2Core/Client/Connection/RequestQueue.swift deleted file mode 100644 index b935e4a05..000000000 --- a/Sources/GRPCHTTP2Core/Client/Connection/RequestQueue.swift +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -internal import DequeModule - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -struct RequestQueue { - typealias Continuation = CheckedContinuation - - private struct QueueEntry { - var continuation: Continuation - var waitForReady: Bool - } - - /// IDs of entries in the order they should be processed. - /// - /// If an ID is popped from the queue but isn't present in `entriesByID` then it must've - /// been removed directly by its ID, this is fine. - private var ids: Deque - - /// Entries keyed by their ID. - private var entriesByID: [QueueEntryID: QueueEntry] - - init() { - self.ids = [] - self.entriesByID = [:] - } - - /// Remove the first continuation from the queue. - mutating func popFirst() -> Continuation? { - while let id = self.ids.popFirst() { - if let waiter = self.entriesByID.removeValue(forKey: id) { - return waiter.continuation - } - } - - assert(self.entriesByID.isEmpty) - return nil - } - - /// Append a continuation to the queue. - /// - /// - Parameters: - /// - continuation: The continuation to append. - /// - waitForReady: Whether the request associated with the continuation is willing to wait for - /// the channel to become ready. - /// - id: The unique ID of the queue entry. - mutating func append(continuation: Continuation, waitForReady: Bool, id: QueueEntryID) { - let entry = QueueEntry(continuation: continuation, waitForReady: waitForReady) - let removed = self.entriesByID.updateValue(entry, forKey: id) - assert(removed == nil, "id '\(id)' reused") - self.ids.append(id) - } - - /// Remove the waiter with the given ID, if it exists. - mutating func removeEntry(withID id: QueueEntryID) -> Continuation? { - let waiter = self.entriesByID.removeValue(forKey: id) - return waiter?.continuation - } - - /// Remove all waiters, returning their continuations. - mutating func removeAll() -> [Continuation] { - let continuations = Array(self.entriesByID.values.map { $0.continuation }) - self.ids.removeAll(keepingCapacity: true) - self.entriesByID.removeAll(keepingCapacity: true) - return continuations - } - - /// Remove all entries which were appended to the queue with a value of `false` - /// for `waitForReady`. - mutating func removeFastFailingEntries() -> [Continuation] { - var removed = [Continuation]() - var remainingIDs = Deque() - var remainingEntriesByID = [QueueEntryID: QueueEntry]() - - while let id = self.ids.popFirst() { - guard let waiter = self.entriesByID.removeValue(forKey: id) else { continue } - - if waiter.waitForReady { - remainingEntriesByID[id] = waiter - remainingIDs.append(id) - } else { - removed.append(waiter.continuation) - } - } - - assert(self.entriesByID.isEmpty) - self.entriesByID = remainingEntriesByID - self.ids = remainingIDs - return removed - } -} diff --git a/Sources/GRPCHTTP2Core/Client/GRPCClientStreamHandler.swift b/Sources/GRPCHTTP2Core/Client/GRPCClientStreamHandler.swift deleted file mode 100644 index e4562e8de..000000000 --- a/Sources/GRPCHTTP2Core/Client/GRPCClientStreamHandler.swift +++ /dev/null @@ -1,302 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -internal import GRPCCore -internal import NIOCore -internal import NIOHTTP2 - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -final class GRPCClientStreamHandler: ChannelDuplexHandler { - typealias InboundIn = HTTP2Frame.FramePayload - typealias InboundOut = RPCResponsePart - - typealias OutboundIn = RPCRequestPart - typealias OutboundOut = HTTP2Frame.FramePayload - - private var stateMachine: GRPCStreamStateMachine - - private var isReading = false - private var flushPending = false - - init( - methodDescriptor: MethodDescriptor, - scheme: Scheme, - outboundEncoding: CompressionAlgorithm, - acceptedEncodings: CompressionAlgorithmSet, - maxPayloadSize: Int, - skipStateMachineAssertions: Bool = false - ) { - self.stateMachine = .init( - configuration: .client( - .init( - methodDescriptor: methodDescriptor, - scheme: scheme, - outboundEncoding: outboundEncoding, - acceptedEncodings: acceptedEncodings - ) - ), - maxPayloadSize: maxPayloadSize, - skipAssertions: skipStateMachineAssertions - ) - } -} - -// - MARK: ChannelInboundHandler - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -extension GRPCClientStreamHandler { - func channelRead(context: ChannelHandlerContext, data: NIOAny) { - self.isReading = true - let frame = self.unwrapInboundIn(data) - switch frame { - case .data(let frameData): - let endStream = frameData.endStream - switch frameData.data { - case .byteBuffer(let buffer): - do { - switch try self.stateMachine.receive(buffer: buffer, endStream: endStream) { - case .endRPCAndForwardErrorStatus_clientOnly(let status): - context.fireChannelRead(self.wrapInboundOut(.status(status, [:]))) - context.close(promise: nil) - - case .forwardErrorAndClose_serverOnly: - assertionFailure("Unexpected client action") - - case .readInbound: - loop: while true { - switch self.stateMachine.nextInboundMessage() { - case .receiveMessage(let message): - context.fireChannelRead(self.wrapInboundOut(.message(message))) - case .awaitMoreMessages: - break loop - case .noMoreMessages: - // This could only happen if the server sends a data frame with EOS - // set, without sending status and trailers. - // If this happens, we should have forwarded an error status above - // so we should never reach this point. Do nothing. - break loop - } - } - - case .doNothing: - () - } - } catch let invalidState { - let error = RPCError(invalidState) - context.fireErrorCaught(error) - } - - case .fileRegion: - preconditionFailure("Unexpected IOData.fileRegion") - } - - case .headers(let headers): - do { - let action = try self.stateMachine.receive( - headers: headers.headers, - endStream: headers.endStream - ) - switch action { - case .receivedMetadata(let metadata, _): - context.fireChannelRead(self.wrapInboundOut(.metadata(metadata))) - - case .receivedStatusAndMetadata_clientOnly(let status, let metadata): - context.fireChannelRead(self.wrapInboundOut(.status(status, metadata))) - context.fireUserInboundEventTriggered(ChannelEvent.inputClosed) - - case .rejectRPC_serverOnly, .protocolViolation_serverOnly: - assertionFailure("Unexpected action '\(action)'") - - case .doNothing: - () - } - } catch let invalidState { - let error = RPCError(invalidState) - context.fireErrorCaught(error) - } - - case .rstStream: - self.handleUnexpectedInboundClose(context: context, reason: .streamReset) - - case .ping, .goAway, .priority, .settings, .pushPromise, .windowUpdate, - .alternativeService, .origin: - () - } - } - - func channelReadComplete(context: ChannelHandlerContext) { - self.isReading = false - if self.flushPending { - self.flushPending = false - self.flush(context: context) - } - context.fireChannelReadComplete() - } - - func handlerRemoved(context: ChannelHandlerContext) { - self.stateMachine.tearDown() - } - - func channelInactive(context: ChannelHandlerContext) { - self.handleUnexpectedInboundClose(context: context, reason: .channelInactive) - context.fireChannelInactive() - } - - func errorCaught(context: ChannelHandlerContext, error: any Error) { - self.handleUnexpectedInboundClose(context: context, reason: .errorThrown(error)) - } - - private func handleUnexpectedInboundClose( - context: ChannelHandlerContext, - reason: GRPCStreamStateMachine.UnexpectedInboundCloseReason - ) { - switch self.stateMachine.unexpectedInboundClose(reason: reason) { - case .forwardStatus_clientOnly(let status): - context.fireChannelRead(self.wrapInboundOut(.status(status, [:]))) - case .doNothing: - () - case .fireError_serverOnly: - assertionFailure("`fireError` should only happen on the server side, never on the client.") - } - } -} - -// - MARK: ChannelOutboundHandler - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -extension GRPCClientStreamHandler { - func write(context: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise?) { - switch self.unwrapOutboundIn(data) { - case .metadata(let metadata): - do { - self.flushPending = true - let headers = try self.stateMachine.send(metadata: metadata) - context.write(self.wrapOutboundOut(.headers(.init(headers: headers))), promise: promise) - } catch let invalidState { - let error = RPCError(invalidState) - promise?.fail(error) - context.fireErrorCaught(error) - } - - case .message(let message): - do { - try self.stateMachine.send(message: message, promise: promise) - } catch let invalidState { - let error = RPCError(invalidState) - promise?.fail(error) - context.fireErrorCaught(error) - } - } - } - - func close(context: ChannelHandlerContext, mode: CloseMode, promise: EventLoopPromise?) { - switch mode { - case .input: - context.fireUserInboundEventTriggered(ChannelEvent.inputClosed) - promise?.succeed() - - case .output: - // We flush all pending messages and update the internal state machine's - // state, but we don't close the outbound end of the channel, because - // forwarding the close in this case would cause the HTTP2 stream handler - // to close the whole channel (as the mode is ignored in its implementation). - do { - try self.stateMachine.closeOutbound() - // Force a flush by calling _flush instead of flush - // (otherwise, we'd skip flushing if we're in a read loop) - self._flush(context: context) - promise?.succeed() - } catch let invalidState { - let error = RPCError(invalidState) - promise?.fail(error) - context.fireErrorCaught(error) - } - - case .all: - // Since we're closing the whole channel here, we *do* forward the close - // down the pipeline. - do { - try self.stateMachine.closeOutbound() - // Force a flush by calling _flush - // (otherwise, we'd skip flushing if we're in a read loop) - self._flush(context: context) - context.close(mode: mode, promise: promise) - } catch let invalidState { - let error = RPCError(invalidState) - promise?.fail(error) - context.fireErrorCaught(error) - } - } - } - - func flush(context: ChannelHandlerContext) { - if self.isReading { - // We don't want to flush yet if we're still in a read loop. - self.flushPending = true - return - } - - self._flush(context: context) - } - - private func _flush(context: ChannelHandlerContext) { - do { - loop: while true { - switch try self.stateMachine.nextOutboundFrame() { - case .sendFrame(let byteBuffer, let promise): - self.flushPending = true - context.write( - self.wrapOutboundOut(.data(.init(data: .byteBuffer(byteBuffer)))), - promise: promise - ) - - case .noMoreMessages: - // Write an empty data frame with the EOS flag set, to signal the RPC - // request is now finished. - context.write( - self.wrapOutboundOut( - HTTP2Frame.FramePayload.data( - .init( - data: .byteBuffer(.init()), - endStream: true - ) - ) - ), - promise: nil - ) - - context.flush() - break loop - - case .awaitMoreMessages: - if self.flushPending { - self.flushPending = false - context.flush() - } - break loop - - case .closeAndFailPromise(let promise, let error): - context.close(mode: .all, promise: nil) - promise?.fail(error) - break loop - } - - } - } catch let invalidState { - context.fireErrorCaught(RPCError(invalidState)) - } - } -} diff --git a/Sources/GRPCHTTP2Core/Client/HTTP2ClientTransport.swift b/Sources/GRPCHTTP2Core/Client/HTTP2ClientTransport.swift deleted file mode 100644 index 03ad634e3..000000000 --- a/Sources/GRPCHTTP2Core/Client/HTTP2ClientTransport.swift +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -public import GRPCCore - -/// A namespace for the HTTP/2 client transport. -public enum HTTP2ClientTransport {} - -extension HTTP2ClientTransport { - /// A namespace for HTTP/2 client transport configuration. - public enum Config {} -} - -extension HTTP2ClientTransport.Config { - public struct Compression: Sendable, Hashable { - /// The default algorithm used for compressing outbound messages. - /// - /// This can be overridden on a per-call basis via `CallOptions`. - public var algorithm: CompressionAlgorithm - - /// Compression algorithms enabled for inbound messages. - /// - /// - Note: `CompressionAlgorithm.none` is always supported, even if it isn't set here. - public var enabledAlgorithms: CompressionAlgorithmSet - - /// Creates a new compression configuration. - /// - /// - SeeAlso: ``defaults``. - public init(algorithm: CompressionAlgorithm, enabledAlgorithms: CompressionAlgorithmSet) { - self.algorithm = algorithm - self.enabledAlgorithms = enabledAlgorithms - } - - /// Default values, compression is disabled. - public static var defaults: Self { - Self(algorithm: .none, enabledAlgorithms: .none) - } - } - - @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) - public struct Keepalive: Sendable, Hashable { - /// The amount of time to wait after reading data before sending a keepalive ping. - /// - /// - Note: The transport may choose to increase this value if it is less than 10 seconds. - public var time: Duration - - /// The amount of time the server has to respond to a keepalive ping before the connection - /// is closed. - public var timeout: Duration - - /// Whether the client sends keepalive pings when there are no calls in progress. - public var allowWithoutCalls: Bool - - /// Creates a new keepalive configuration. - public init(time: Duration, timeout: Duration, allowWithoutCalls: Bool) { - self.time = time - self.timeout = timeout - self.allowWithoutCalls = allowWithoutCalls - } - } - - @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) - public struct Connection: Sendable, Hashable { - /// The maximum amount of time a connection may be idle before it's closed. - /// - /// Connections are considered idle when there are no open streams on them. Idle connections - /// can be closed after a configured amount of time to free resources. Note that servers may - /// separately monitor and close idle connections. - public var maxIdleTime: Duration? - - /// Configuration for keepalive. - /// - /// Keepalive is typically applied to connection which have open streams. It can be useful to - /// detect dropped connections, particularly if the streams running on a connection don't have - /// much activity. - /// - /// See also: gRFC A8: Client-side Keepalive. - public var keepalive: Keepalive? - - /// Creates a connection configuration. - public init(maxIdleTime: Duration, keepalive: Keepalive?) { - self.maxIdleTime = maxIdleTime - self.keepalive = keepalive - } - - /// Default values, a 30 minute max idle time and no keepalive. - public static var defaults: Self { - Self(maxIdleTime: .seconds(30 * 60), keepalive: nil) - } - } - - @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) - public struct Backoff: Sendable, Hashable { - /// The initial duration to wait before reattempting to establish a connection. - public var initial: Duration - - /// The maximum duration to wait (before jitter is applied) to wait between connect attempts. - public var max: Duration - - /// The scaling factor applied to the backoff duration between connect attempts. - public var multiplier: Double - - /// An amount to randomize the backoff by. - /// - /// If backoff is computed to be 10 seconds and jitter is set to `0.2`, then the amount of - /// jitter will be selected randomly from the range `-0.2 ✕ 10` seconds to `0.2 ✕ 10` seconds. - /// The resulting backoff will therefore be between 8 seconds and 12 seconds. - public var jitter: Double - - /// Creates a new backoff configuration. - public init(initial: Duration, max: Duration, multiplier: Double, jitter: Double) { - self.initial = initial - self.max = max - self.multiplier = multiplier - self.jitter = jitter - } - - /// Default values, initial backoff is one second and maximum back off is two minutes. The - /// multiplier is `1.6` and the jitter is set to `0.2`. - public static var defaults: Self { - Self(initial: .seconds(1), max: .seconds(120), multiplier: 1.6, jitter: 0.2) - } - } - - public struct HTTP2: Sendable, Hashable { - /// The max frame size, in bytes. - /// - /// The actual value used is clamped to `(1 << 14) ... (1 << 24) - 1` (the min and max values - /// allowed by RFC 9113 § 6.5.2). - public var maxFrameSize: Int - - /// The target flow control window size, in bytes. - /// - /// The value is clamped to `... (1 << 31) - 1`. - public var targetWindowSize: Int - - /// Creates a new HTTP/2 configuration. - public init(maxFrameSize: Int, targetWindowSize: Int) { - self.maxFrameSize = maxFrameSize - self.targetWindowSize = targetWindowSize - } - - /// Default values, max frame size is 16KiB, and the target window size is 8MiB. - public static var defaults: Self { - Self(maxFrameSize: 1 << 14, targetWindowSize: 8 * 1024 * 1024) - } - } -} diff --git a/Sources/GRPCHTTP2Core/Client/Resolver/NameResolver+IPv4.swift b/Sources/GRPCHTTP2Core/Client/Resolver/NameResolver+IPv4.swift deleted file mode 100644 index d3b66ea18..000000000 --- a/Sources/GRPCHTTP2Core/Client/Resolver/NameResolver+IPv4.swift +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -internal import GRPCCore - -extension ResolvableTargets { - /// A resolvable target for IPv4 addresses. - /// - /// IPv4 addresses can be resolved by the ``NameResolvers/IPv4`` resolver which creates a - /// separate ``Endpoint`` for each address. - public struct IPv4: ResolvableTarget { - /// The IPv4 addresses. - public var addresses: [SocketAddress.IPv4] - - /// Create a new IPv4 target. - /// - Parameter addresses: The IPv4 addresses. - public init(addresses: [SocketAddress.IPv4]) { - self.addresses = addresses - } - } -} - -extension ResolvableTarget where Self == ResolvableTargets.IPv4 { - /// Creates a new resolvable IPv4 target for a single address. - /// - Parameters: - /// - host: The host address. - /// - port: The port on the host. - /// - Returns: A ``ResolvableTarget``. - public static func ipv4(host: String, port: Int = 443) -> Self { - let address = SocketAddress.IPv4(host: host, port: port) - return Self(addresses: [address]) - } - - /// Creates a new resolvable IPv4 target from the provided host-port pairs. - /// - /// - Parameter pairs: An array of host-port pairs. - /// - Returns: A ``ResolvableTarget``. - public static func ipv4(pairs: [(host: String, port: Int)]) -> Self { - let address = pairs.map { SocketAddress.IPv4(host: $0.host, port: $0.port) } - return Self(addresses: address) - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension NameResolvers { - /// A ``NameResolverFactory`` for ``ResolvableTargets/IPv4`` targets. - /// - /// The name resolver for a given target always produces the same values, with one endpoint per - /// address in the target. This resolver doesn't support fetching service configuration. - public struct IPv4: NameResolverFactory { - public typealias Target = ResolvableTargets.IPv4 - - /// Create a new IPv4 resolver factory. - public init() {} - - public func resolver(for target: Target) -> NameResolver { - let endpoints = target.addresses.map { Endpoint(addresses: [.ipv4($0)]) } - let resolutionResult = NameResolutionResult(endpoints: endpoints, serviceConfig: nil) - return NameResolver(names: .constant(resolutionResult), updateMode: .pull) - } - } -} diff --git a/Sources/GRPCHTTP2Core/Client/Resolver/NameResolver+IPv6.swift b/Sources/GRPCHTTP2Core/Client/Resolver/NameResolver+IPv6.swift deleted file mode 100644 index 6c41a0353..000000000 --- a/Sources/GRPCHTTP2Core/Client/Resolver/NameResolver+IPv6.swift +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -internal import GRPCCore - -extension ResolvableTargets { - /// A resolvable target for IPv4 addresses. - /// - /// IPv4 addresses can be resolved by the ``NameResolvers/IPv6`` resolver which creates a - /// separate ``Endpoint`` for each address. - public struct IPv6: ResolvableTarget { - /// The IPv6 addresses. - public var addresses: [SocketAddress.IPv6] - - /// Create a new IPv6 target. - /// - Parameter addresses: The IPv6 addresses. - public init(addresses: [SocketAddress.IPv6]) { - self.addresses = addresses - } - } -} - -extension ResolvableTarget where Self == ResolvableTargets.IPv6 { - /// Creates a new resolvable IPv6 target for a single address. - /// - Parameters: - /// - host: The host address. - /// - port: The port on the host. - /// - Returns: A ``ResolvableTarget``. - public static func ipv6(host: String, port: Int = 443) -> Self { - let address = SocketAddress.IPv6(host: host, port: port) - return Self(addresses: [address]) - } - - /// Creates a new resolvable IPv6 target from the provided host-port pairs. - /// - /// - Parameter pairs: An array of host-port pairs. - /// - Returns: A ``ResolvableTarget``. - public static func ipv6(pairs: [(host: String, port: Int)]) -> Self { - let address = pairs.map { SocketAddress.IPv6(host: $0.host, port: $0.port) } - return Self(addresses: address) - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension NameResolvers { - /// A ``NameResolverFactory`` for ``ResolvableTargets/IPv6`` targets. - /// - /// The name resolver for a given target always produces the same values, with one endpoint per - /// address in the target. This resolver doesn't support fetching service configuration. - public struct IPv6: NameResolverFactory { - public typealias Target = ResolvableTargets.IPv6 - - /// Create a new IPv6 resolver factory. - public init() {} - - public func resolver(for target: Target) -> NameResolver { - let endpoints = target.addresses.map { Endpoint(addresses: [.ipv6($0)]) } - let resolutionResult = NameResolutionResult(endpoints: endpoints, serviceConfig: nil) - return NameResolver(names: .constant(resolutionResult), updateMode: .pull) - } - } -} diff --git a/Sources/GRPCHTTP2Core/Client/Resolver/NameResolver+UDS.swift b/Sources/GRPCHTTP2Core/Client/Resolver/NameResolver+UDS.swift deleted file mode 100644 index b957bffd5..000000000 --- a/Sources/GRPCHTTP2Core/Client/Resolver/NameResolver+UDS.swift +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -internal import GRPCCore - -extension ResolvableTargets { - /// A resolvable target for Unix Domain Socket address. - /// - /// ``UnixDomainSocket`` addresses can be resolved by the ``NameResolvers/UnixDomainSocket`` - /// resolver which creates a single ``Endpoint`` for target address. - public struct UnixDomainSocket: ResolvableTarget { - /// The Unix Domain Socket address. - public var address: SocketAddress.UnixDomainSocket - - /// Create a new Unix Domain Socket address. - public init(address: SocketAddress.UnixDomainSocket) { - self.address = address - } - } -} - -extension ResolvableTarget where Self == ResolvableTargets.UnixDomainSocket { - /// Creates a new resolvable Unix Domain Socket target. - /// - Parameter path: The path of the socket. - public static func unixDomainSocket(path: String) -> Self { - return Self(address: SocketAddress.UnixDomainSocket(path: path)) - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension NameResolvers { - /// A ``NameResolverFactory`` for ``ResolvableTargets/UnixDomainSocket`` targets. - /// - /// The name resolver for a given target always produces the same values, with a single endpoint. - /// This resolver doesn't support fetching service configuration. - public struct UnixDomainSocket: NameResolverFactory { - public typealias Target = ResolvableTargets.UnixDomainSocket - - public init() {} - - public func resolver(for target: Target) -> NameResolver { - let endpoint = Endpoint(addresses: [.unixDomainSocket(target.address)]) - let resolutionResult = NameResolutionResult(endpoints: [endpoint], serviceConfig: nil) - return NameResolver(names: .constant(resolutionResult), updateMode: .pull) - } - } -} diff --git a/Sources/GRPCHTTP2Core/Client/Resolver/NameResolver+VSOCK.swift b/Sources/GRPCHTTP2Core/Client/Resolver/NameResolver+VSOCK.swift deleted file mode 100644 index e8c6c815b..000000000 --- a/Sources/GRPCHTTP2Core/Client/Resolver/NameResolver+VSOCK.swift +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -internal import GRPCCore - -extension ResolvableTargets { - /// A resolvable target for Virtual Socket addresses. - /// - /// ``VirtualSocket`` addresses can be resolved by the ``NameResolvers/VirtualSocket`` - /// resolver which creates a single ``Endpoint`` for target address. - public struct VirtualSocket: ResolvableTarget { - public var address: SocketAddress.VirtualSocket - - public init(address: SocketAddress.VirtualSocket) { - self.address = address - } - } -} - -extension ResolvableTarget where Self == ResolvableTargets.VirtualSocket { - /// Creates a new resolvable Virtual Socket target. - /// - Parameters: - /// - contextID: The context ID ('cid') of the service. - /// - port: The port to connect to. - public static func vsock( - contextID: SocketAddress.VirtualSocket.ContextID, - port: SocketAddress.VirtualSocket.Port - ) -> Self { - let address = SocketAddress.VirtualSocket(contextID: contextID, port: port) - return ResolvableTargets.VirtualSocket(address: address) - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension NameResolvers { - /// A ``NameResolverFactory`` for ``ResolvableTargets/VirtualSocket`` targets. - /// - /// The name resolver for a given target always produces the same values, with a single endpoint. - /// This resolver doesn't support fetching service configuration. - public struct VirtualSocket: NameResolverFactory { - public typealias Target = ResolvableTargets.VirtualSocket - - public init() {} - - public func resolver(for target: Target) -> NameResolver { - let endpoint = Endpoint(addresses: [.vsock(target.address)]) - let resolutionResult = NameResolutionResult(endpoints: [endpoint], serviceConfig: nil) - return NameResolver(names: .constant(resolutionResult), updateMode: .pull) - } - } -} diff --git a/Sources/GRPCHTTP2Core/Client/Resolver/NameResolver.swift b/Sources/GRPCHTTP2Core/Client/Resolver/NameResolver.swift deleted file mode 100644 index 822ddb815..000000000 --- a/Sources/GRPCHTTP2Core/Client/Resolver/NameResolver.swift +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -public import GRPCCore - -/// A name resolver can provide resolved addresses and service configuration values over time. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -public struct NameResolver: Sendable { - /// A sequence of name resolution results. - /// - /// Resolvers may be push or pull based. Resolvers with the ``UpdateMode-swift.struct/push`` - /// update mode have addresses pushed to them by an external source and you should subscribe - /// to changes in addresses by awaiting for new values in a loop. - /// - /// Resolvers with the ``UpdateMode-swift.struct/pull`` update mode shouldn't be subscribed to, - /// instead you should create an iterator and ask for new results as and when necessary. - public var names: RPCAsyncSequence - - /// How ``names`` is updated and should be consumed. - public let updateMode: UpdateMode - - public struct UpdateMode: Hashable, Sendable { - enum Value: Hashable, Sendable { - case push - case pull - } - - let value: Value - - private init(_ value: Value) { - self.value = value - } - - /// Addresses are pushed to the resolve by an external source. - public static var push: Self { Self(.push) } - - /// Addresses are resolved lazily, when the caller asks them to be resolved. - public static var pull: Self { Self(.pull) } - } - - /// Create a new name resolver. - public init(names: RPCAsyncSequence, updateMode: UpdateMode) { - self.names = names - self.updateMode = updateMode - } -} - -/// The result of name resolution, a list of endpoints to connect to and the service -/// configuration reported by the resolver. -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -public struct NameResolutionResult: Hashable, Sendable { - /// A list of endpoints to connect to. - public var endpoints: [Endpoint] - - /// The service configuration reported by the resolver, or an error if it couldn't be parsed. - /// This value may be `nil` if the resolver doesn't support fetching service configuration. - public var serviceConfig: Result? - - public init( - endpoints: [Endpoint], - serviceConfig: Result? - ) { - self.endpoints = endpoints - self.serviceConfig = serviceConfig - } -} - -/// A group of addresses which are considered equivalent when establishing a connection. -public struct Endpoint: Hashable, Sendable { - /// A list of equivalent addresses. - /// - /// Earlier addresses are typically but not always connected to first. Some load balancers may - /// choose to ignore the order. - public var addresses: [SocketAddress] - - /// Create a new ``Endpoint``. - /// - Parameter addresses: A list of equivalent addresses. - public init(addresses: [SocketAddress]) { - self.addresses = addresses - } -} - -/// A resolver capable of resolving targets of type ``Target``. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -public protocol NameResolverFactory { - /// The type of ``ResolvableTarget`` this factory makes resolvers for. - associatedtype Target: ResolvableTarget - - /// Creates a resolver for the given target. - /// - /// - Parameter target: The target to make a resolver for. - /// - Returns: The name resolver for the target. - func resolver(for target: Target) -> NameResolver -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension NameResolverFactory { - /// Returns whether the given target is compatible with this factory. - /// - /// - Parameter target: The target to check the compatibility of. - /// - Returns: Whether the target is compatible with this factory. - func isCompatible(withTarget target: Other) -> Bool { - return target is Target - } - - /// Returns a name resolver if the given target is compatible. - /// - /// - Parameter target: The target to make a name resolver for. - /// - Returns: A name resolver or `nil` if the target isn't compatible. - func makeResolverIfCompatible(_ target: Other) -> NameResolver? { - guard let target = target as? Target else { return nil } - return self.resolver(for: target) - } -} - -/// A target which can be resolved to a ``SocketAddress``. -public protocol ResolvableTarget {} - -/// A namespace for resolvable targets. -public enum ResolvableTargets {} - -/// A namespace for name resolver factories. -public enum NameResolvers {} diff --git a/Sources/GRPCHTTP2Core/Client/Resolver/NameResolverRegistry.swift b/Sources/GRPCHTTP2Core/Client/Resolver/NameResolverRegistry.swift deleted file mode 100644 index c8e847196..000000000 --- a/Sources/GRPCHTTP2Core/Client/Resolver/NameResolverRegistry.swift +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -/// A registry for name resolver factories. -/// -/// The registry provides name resolvers for resolvable targets. You can control which name -/// resolvers are available by registering and removing resolvers by type. The following code -/// demonstrates how to create a registry, add and remove resolver factories, and create a resolver. -/// -/// ```swift -/// // Create a new resolver registry with the default resolvers. -/// var registry = NameResolverRegistry.defaults -/// -/// // Register a custom resolver, the registry can now resolve targets of -/// // type `CustomResolver.ResolvableTarget`. -/// registry.registerFactory(CustomResolver()) -/// -/// // Remove the Unix Domain Socket and Virtual Socket resolvers, if they exist. -/// registry.removeFactory(ofType: NameResolvers.UnixDomainSocket.self) -/// registry.removeFactory(ofType: NameResolvers.VirtualSocket.self) -/// -/// // Resolve an IPv4 target -/// if let resolver = registry.makeResolver(for: .ipv4(host: "localhost", port: 80)) { -/// // ... -/// } -/// ``` -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -public struct NameResolverRegistry { - private enum Factory { - case ipv4(NameResolvers.IPv4) - case ipv6(NameResolvers.IPv6) - case unix(NameResolvers.UnixDomainSocket) - case vsock(NameResolvers.VirtualSocket) - case other(any NameResolverFactory) - - init(_ factory: some NameResolverFactory) { - if let ipv4 = factory as? NameResolvers.IPv4 { - self = .ipv4(ipv4) - } else if let ipv6 = factory as? NameResolvers.IPv6 { - self = .ipv6(ipv6) - } else if let unix = factory as? NameResolvers.UnixDomainSocket { - self = .unix(unix) - } else if let vsock = factory as? NameResolvers.VirtualSocket { - self = .vsock(vsock) - } else { - self = .other(factory) - } - } - - func makeResolverIfCompatible(_ target: Target) -> NameResolver? { - switch self { - case .ipv4(let factory): - return factory.makeResolverIfCompatible(target) - case .ipv6(let factory): - return factory.makeResolverIfCompatible(target) - case .unix(let factory): - return factory.makeResolverIfCompatible(target) - case .vsock(let factory): - return factory.makeResolverIfCompatible(target) - case .other(let factory): - return factory.makeResolverIfCompatible(target) - } - } - - func hasTarget(_ target: Target) -> Bool { - switch self { - case .ipv4(let factory): - return factory.isCompatible(withTarget: target) - case .ipv6(let factory): - return factory.isCompatible(withTarget: target) - case .unix(let factory): - return factory.isCompatible(withTarget: target) - case .vsock(let factory): - return factory.isCompatible(withTarget: target) - case .other(let factory): - return factory.isCompatible(withTarget: target) - } - } - - func `is`(ofType factoryType: Factory.Type) -> Bool { - switch self { - case .ipv4: - return NameResolvers.IPv4.self == factoryType - case .ipv6: - return NameResolvers.IPv6.self == factoryType - case .unix: - return NameResolvers.UnixDomainSocket.self == factoryType - case .vsock: - return NameResolvers.VirtualSocket.self == factoryType - case .other(let factory): - return type(of: factory) == factoryType - } - } - } - - private var factories: [Factory] - - /// Creates a new name resolver registry with no resolve factories. - public init() { - self.factories = [] - } - - /// Returns a new name resolver registry with the default factories registered. - /// - /// The default resolvers include: - /// - ``NameResolvers/IPv4``, - /// - ``NameResolvers/IPv6``, - /// - ``NameResolvers/UnixDomainSocket``, - /// - ``NameResolvers/VirtualSocket``. - public static var defaults: Self { - var resolvers = NameResolverRegistry() - resolvers.registerFactory(NameResolvers.IPv4()) - resolvers.registerFactory(NameResolvers.IPv6()) - resolvers.registerFactory(NameResolvers.UnixDomainSocket()) - resolvers.registerFactory(NameResolvers.VirtualSocket()) - return resolvers - } - - /// The number of resolver factories in the registry. - public var count: Int { - return self.factories.count - } - - /// Whether there are no resolver factories in the registry. - public var isEmpty: Bool { - return self.factories.isEmpty - } - - /// Registers a new name resolver factory. - /// - /// Any factories of the same type are removed prior to inserting the factory. - /// - /// - Parameter factory: The factory to register. - public mutating func registerFactory(_ factory: Factory) { - self.removeFactory(ofType: Factory.self) - self.factories.append(Self.Factory(factory)) - } - - /// Removes any factories which have the given type - /// - /// - Parameter type: The type of factory to remove. - /// - Returns: Whether a factory was removed. - @discardableResult - public mutating func removeFactory( - ofType type: Factory.Type - ) -> Bool { - let factoryCount = self.factories.count - self.factories.removeAll { - $0.is(ofType: Factory.self) - } - return self.factories.count < factoryCount - } - - /// Returns whether the registry contains a factory of the given type. - /// - /// - Parameter type: The type of factory to look for. - /// - Returns: Whether the registry contained the factory of the given type. - public func containsFactory(ofType type: Factory.Type) -> Bool { - self.factories.contains { - $0.is(ofType: Factory.self) - } - } - - /// Returns whether the registry contains a factory capable of resolving the given target. - /// - /// - Parameter target: - /// - Returns: Whether the registry contains a resolve capable of resolving the target. - public func containsFactory(capableOfResolving target: some ResolvableTarget) -> Bool { - self.factories.contains { $0.hasTarget(target) } - } - - /// Makes a ``NameResolver`` for the target, if a suitable factory exists. - /// - /// If multiple factories exist which are capable of resolving the target then the first - /// is used. - /// - /// - Parameter target: The target to make a resolver for. - /// - Returns: The resolver, or `nil` if no factory could make a resolver for the target. - public func makeResolver(for target: some ResolvableTarget) -> NameResolver? { - for factory in self.factories { - if let resolver = factory.makeResolverIfCompatible(target) { - return resolver - } - } - return nil - } -} diff --git a/Sources/GRPCHTTP2Core/Client/Resolver/SocketAddress.swift b/Sources/GRPCHTTP2Core/Client/Resolver/SocketAddress.swift deleted file mode 100644 index 8f5d961b1..000000000 --- a/Sources/GRPCHTTP2Core/Client/Resolver/SocketAddress.swift +++ /dev/null @@ -1,313 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -/// An address to which a socket may connect or bind. -public struct SocketAddress: Hashable, Sendable { - private enum Value: Hashable, Sendable { - case ipv4(IPv4) - case ipv6(IPv6) - case unix(UnixDomainSocket) - case vsock(VirtualSocket) - } - - private var value: Value - private init(_ value: Value) { - self.value = value - } - - /// Returns the address as an IPv4 address, if possible. - public var ipv4: IPv4? { - switch self.value { - case .ipv4(let address): - return address - default: - return nil - } - } - - /// Returns the address as an IPv6 address, if possible. - public var ipv6: IPv6? { - switch self.value { - case .ipv6(let address): - return address - default: - return nil - } - } - - /// Returns the address as an Unix domain socket address, if possible. - public var unixDomainSocket: UnixDomainSocket? { - switch self.value { - case .unix(let address): - return address - default: - return nil - } - } - - /// Returns the address as an VSOCK address, if possible. - public var virtualSocket: VirtualSocket? { - switch self.value { - case .vsock(let address): - return address - default: - return nil - } - } -} - -extension SocketAddress { - /// Creates a socket address by wrapping a ``SocketAddress/IPv4-swift.struct``. - public static func ipv4(_ address: IPv4) -> Self { - return Self(.ipv4(address)) - } - - /// Creates a socket address by wrapping a ``SocketAddress/IPv6-swift.struct``. - public static func ipv6(_ address: IPv6) -> Self { - return Self(.ipv6(address)) - } - - /// Creates a socket address by wrapping a ``SocketAddress/UnixDomainSocket-swift.struct``. - public static func unixDomainSocket(_ address: UnixDomainSocket) -> Self { - return Self(.unix(address)) - } - - /// Creates a socket address by wrapping a ``SocketAddress/VirtualSocket-swift.struct``. - public static func vsock(_ address: VirtualSocket) -> Self { - return Self(.vsock(address)) - } -} - -extension SocketAddress { - /// Creates an IPv4 socket address. - public static func ipv4(host: String, port: Int) -> Self { - return .ipv4(IPv4(host: host, port: port)) - } - - /// Creates an IPv6 socket address. - public static func ipv6(host: String, port: Int) -> Self { - return .ipv6(IPv6(host: host, port: port)) - } - /// Creates a Unix socket address. - public static func unixDomainSocket(path: String) -> Self { - return .unixDomainSocket(UnixDomainSocket(path: path)) - } - - /// Create a Virtual Socket ('vsock') address. - public static func vsock(contextID: VirtualSocket.ContextID, port: VirtualSocket.Port) -> Self { - return .vsock(VirtualSocket(contextID: contextID, port: port)) - } -} - -extension SocketAddress: CustomStringConvertible { - public var description: String { - switch self.value { - case .ipv4(let address): - return String(describing: address) - case .ipv6(let address): - return String(describing: address) - case .unix(let address): - return String(describing: address) - case .vsock(let address): - return String(describing: address) - } - } -} - -extension SocketAddress { - public struct IPv4: Hashable, Sendable { - /// The resolved host address. - public var host: String - /// The port to connect to. - public var port: Int - - /// Creates a new IPv4 address. - /// - /// - Parameters: - /// - host: Resolved host address. - /// - port: Port to connect to. - public init(host: String, port: Int) { - self.host = host - self.port = port - } - } - - public struct IPv6: Hashable, Sendable { - /// The resolved host address. - public var host: String - /// The port to connect to. - public var port: Int - - /// Creates a new IPv6 address. - /// - /// - Parameters: - /// - host: Resolved host address. - /// - port: Port to connect to. - public init(host: String, port: Int) { - self.host = host - self.port = port - } - } - - public struct UnixDomainSocket: Hashable, Sendable { - /// The path name of the Unix domain socket. - public var path: String - - /// Create a new Unix domain socket address. - /// - /// - Parameter path: The path name of the Unix domain socket. - public init(path: String) { - self.path = path - } - } - - public struct VirtualSocket: Hashable, Sendable { - /// A context identifier. - /// - /// Indicates the source or destination which is either a virtual machine or the host. - public var contextID: ContextID - - /// The port number. - public var port: Port - - /// Create a new VSOCK address. - /// - /// - Parameters: - /// - contextID: The context ID (or 'cid') of the address. - /// - port: The port number. - public init(contextID: ContextID, port: Port) { - self.contextID = contextID - self.port = port - } - - public struct Port: Hashable, Sendable, RawRepresentable, ExpressibleByIntegerLiteral { - /// The port number. - public var rawValue: UInt32 - - public init(rawValue: UInt32) { - self.rawValue = rawValue - } - - public init(integerLiteral value: UInt32) { - self.rawValue = value - } - - public init(_ value: Int) { - self.init(rawValue: UInt32(bitPattern: Int32(truncatingIfNeeded: value))) - } - - /// Used to bind to any port number. - /// - /// This is equal to `VMADDR_PORT_ANY (-1U)`. - public static var any: Self { - Self(rawValue: UInt32(bitPattern: -1)) - } - } - - public struct ContextID: Hashable, Sendable, RawRepresentable, ExpressibleByIntegerLiteral { - /// The context identifier. - public var rawValue: UInt32 - - public init(rawValue: UInt32) { - self.rawValue = rawValue - } - - public init(integerLiteral value: UInt32) { - self.rawValue = value - } - - public init(_ value: Int) { - self.rawValue = UInt32(bitPattern: Int32(truncatingIfNeeded: value)) - } - - /// Wildcard, matches any address. - /// - /// On all platforms, using this value with `bind(2)` means "any address". - /// - /// On Darwin platforms, the man page states this can be used with `connect(2)` - /// to mean "this host". - /// - /// This is equal to `VMADDR_CID_ANY (-1U)`. - public static var any: Self { - Self(rawValue: UInt32(bitPattern: -1)) - } - - /// The address of the hypervisor. - /// - /// This is equal to `VMADDR_CID_HYPERVISOR (0)`. - public static var hypervisor: Self { - Self(rawValue: 0) - } - - /// The address of the host. - /// - /// This is equal to `VMADDR_CID_HOST (2)`. - public static var host: Self { - Self(rawValue: 2) - } - - /// The address for local communication (loopback). - /// - /// This directs packets to the same host that generated them. This is useful for testing - /// applications on a single host and for debugging. - /// - /// This is equal to `VMADDR_CID_LOCAL (1)` on platforms that define it. - /// - /// - Warning: `VMADDR_CID_LOCAL (1)` is available from Linux 5.6. Its use is unsupported on - /// other platforms. - /// - SeeAlso: https://man7.org/linux/man-pages/man7/vsock.7.html - public static var local: Self { - Self(rawValue: 1) - } - } - } -} - -extension SocketAddress.IPv4: CustomStringConvertible { - public var description: String { - "[ipv4]\(self.host):\(self.port)" - } -} - -extension SocketAddress.IPv6: CustomStringConvertible { - public var description: String { - "[ipv6]\(self.host):\(self.port)" - } -} - -extension SocketAddress.UnixDomainSocket: CustomStringConvertible { - public var description: String { - "[unix]\(self.path)" - } -} - -extension SocketAddress.VirtualSocket: CustomStringConvertible { - public var description: String { - "[vsock]\(self.contextID):\(self.port)" - } -} - -extension SocketAddress.VirtualSocket.ContextID: CustomStringConvertible { - public var description: String { - self == .any ? "-1" : String(describing: self.rawValue) - } -} - -extension SocketAddress.VirtualSocket.Port: CustomStringConvertible { - public var description: String { - self == .any ? "-1" : String(describing: self.rawValue) - } -} diff --git a/Sources/GRPCHTTP2Core/Compression/CompressionAlgorithm.swift b/Sources/GRPCHTTP2Core/Compression/CompressionAlgorithm.swift deleted file mode 100644 index 1608f4c1e..000000000 --- a/Sources/GRPCHTTP2Core/Compression/CompressionAlgorithm.swift +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -internal import GRPCCore - -extension CompressionAlgorithm { - init?(name: String) { - self.init(name: name[...]) - } - - init?(name: Substring) { - switch name { - case "gzip": - self = .gzip - case "deflate": - self = .deflate - case "identity": - self = .none - default: - return nil - } - } - - var name: String { - switch self.value { - case .gzip: - return "gzip" - case .deflate: - return "deflate" - case .none: - return "identity" - } - } -} - -extension CompressionAlgorithmSet { - var count: Int { - self.rawValue.nonzeroBitCount - } -} diff --git a/Sources/GRPCHTTP2Core/Compression/Zlib.swift b/Sources/GRPCHTTP2Core/Compression/Zlib.swift deleted file mode 100644 index a2a25110f..000000000 --- a/Sources/GRPCHTTP2Core/Compression/Zlib.swift +++ /dev/null @@ -1,419 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -internal import CGRPCZlib -internal import GRPCCore -internal import NIOCore - -enum Zlib { - enum Method { - case deflate - case gzip - - fileprivate var windowBits: Int32 { - switch self { - case .deflate: - return 15 - case .gzip: - return 31 - } - } - } -} - -extension Zlib { - /// Creates a new compressor for the given compression format. - /// - /// This compressor is only suitable for compressing whole messages at a time. - /// - /// - Important: ``Compressor/end()`` must be called when the compressor is not needed - /// anymore, to deallocate any resources allocated by `Zlib`. - struct Compressor { - // TODO: Make this ~Copyable when 5.9 is the lowest supported Swift version. - - private var stream: UnsafeMutablePointer - private let method: Method - - init(method: Method) { - self.method = method - self.stream = .allocate(capacity: 1) - self.stream.initialize(to: z_stream()) - self.stream.deflateInit(windowBits: self.method.windowBits) - } - - /// Compresses the data in `input` into the `output` buffer. - /// - /// - Parameter input: The complete data to be compressed. - /// - Parameter output: The `ByteBuffer` into which the compressed message should be written. - /// - Returns: The number of bytes written into the `output` buffer. - @discardableResult - func compress(_ input: [UInt8], into output: inout ByteBuffer) throws(ZlibError) -> Int { - defer { self.reset() } - let upperBound = self.stream.deflateBound(inputBytes: input.count) - return try self.stream.deflate(input, into: &output, upperBound: upperBound) - } - - /// Resets compression state. - private func reset() { - do { - try self.stream.deflateReset() - } catch { - self.end() - self.stream.initialize(to: z_stream()) - self.stream.deflateInit(windowBits: self.method.windowBits) - } - } - - /// Deallocates any resources allocated by Zlib. - func end() { - self.stream.deflateEnd() - self.stream.deallocate() - } - } -} - -extension Zlib { - /// Creates a new decompressor for the given compression format. - /// - /// This decompressor is only suitable for compressing whole messages at a time. - /// - /// - Important: ``Decompressor/end()`` must be called when the compressor is not needed - /// anymore, to deallocate any resources allocated by `Zlib`. - struct Decompressor { - // TODO: Make this ~Copyable when 5.9 is the lowest supported Swift version. - - private var stream: UnsafeMutablePointer - private let method: Method - - init(method: Method) { - self.method = method - self.stream = UnsafeMutablePointer.allocate(capacity: 1) - self.stream.initialize(to: z_stream()) - self.stream.inflateInit(windowBits: self.method.windowBits) - } - - /// Returns the decompressed bytes from ``input``. - /// - /// - Parameters: - /// - input: The buffer read compressed bytes from. - /// - limit: The largest size a decompressed payload may be. - func decompress(_ input: inout ByteBuffer, limit: Int) throws -> [UInt8] { - defer { self.reset() } - return try self.stream.inflate(input: &input, limit: limit) - } - - /// Resets decompression state. - private func reset() { - do { - try self.stream.inflateReset() - } catch { - self.end() - self.stream.initialize(to: z_stream()) - self.stream.inflateInit(windowBits: self.method.windowBits) - } - } - - /// Deallocates any resources allocated by Zlib. - func end() { - self.stream.inflateEnd() - self.stream.deallocate() - } - } -} - -struct ZlibError: Error, Hashable { - /// Error code returned from Zlib. - var code: Int - /// Error message produced by Zlib. - var message: String - - init(code: Int, message: String) { - self.code = code - self.message = message - } -} - -extension UnsafeMutablePointer { - func inflateInit(windowBits: Int32) { - self.pointee.zfree = nil - self.pointee.zalloc = nil - self.pointee.opaque = nil - - let rc = CGRPCZlib_inflateInit2(self, windowBits) - // Possible return codes: - // - Z_OK - // - Z_MEM_ERROR: not enough memory - // - // If we can't allocate memory then we can't progress anyway so not throwing an error here is - // okay. - precondition(rc == Z_OK, "inflateInit2 failed with error (\(rc)) \(self.lastError ?? "")") - } - - func inflateReset() throws { - let rc = CGRPCZlib_inflateReset(self) - - // Possible return codes: - // - Z_OK - // - Z_STREAM_ERROR: the source stream state was inconsistent. - switch rc { - case Z_OK: - () - case Z_STREAM_ERROR: - throw ZlibError(code: Int(rc), message: self.lastError ?? "") - default: - preconditionFailure("inflateReset returned unexpected code (\(rc))") - } - } - - func inflateEnd() { - _ = CGRPCZlib_inflateEnd(self) - } - - func deflateInit(windowBits: Int32) { - self.pointee.zfree = nil - self.pointee.zalloc = nil - self.pointee.opaque = nil - - let rc = CGRPCZlib_deflateInit2( - self, - Z_DEFAULT_COMPRESSION, // compression level - Z_DEFLATED, // compression method (this must be Z_DEFLATED) - windowBits, // window size, i.e. deflate/gzip - 8, // memory level (this is the default value in the docs) - Z_DEFAULT_STRATEGY // compression strategy - ) - - // Possible return codes: - // - Z_OK - // - Z_MEM_ERROR: not enough memory - // - Z_STREAM_ERROR: a parameter was invalid - // - // If we can't allocate memory then we can't progress anyway, and we control the parameters - // so not throwing an error here is okay. - precondition(rc == Z_OK, "deflateInit2 failed with error (\(rc)) \(self.lastError ?? "")") - } - - func deflateReset() throws { - let rc = CGRPCZlib_deflateReset(self) - - // Possible return codes: - // - Z_OK - // - Z_STREAM_ERROR: the source stream state was inconsistent. - switch rc { - case Z_OK: - () - case Z_STREAM_ERROR: - throw ZlibError(code: Int(rc), message: self.lastError ?? "") - default: - preconditionFailure("deflateReset returned unexpected code (\(rc))") - } - } - - func deflateEnd() { - _ = CGRPCZlib_deflateEnd(self) - } - - func deflateBound(inputBytes: Int) -> Int { - let bound = CGRPCZlib_deflateBound(self, UInt(inputBytes)) - return Int(bound) - } - - func setNextInputBuffer(_ buffer: UnsafeMutableBufferPointer) { - if let baseAddress = buffer.baseAddress { - self.pointee.next_in = baseAddress - self.pointee.avail_in = UInt32(buffer.count) - } else { - self.pointee.next_in = nil - self.pointee.avail_in = 0 - } - } - - func setNextInputBuffer(_ buffer: UnsafeMutableRawBufferPointer?) { - if let buffer = buffer, let baseAddress = buffer.baseAddress { - self.pointee.next_in = CGRPCZlib_castVoidToBytefPointer(baseAddress) - self.pointee.avail_in = UInt32(buffer.count) - } else { - self.pointee.next_in = nil - self.pointee.avail_in = 0 - } - } - - func setNextOutputBuffer(_ buffer: UnsafeMutableBufferPointer) { - if let baseAddress = buffer.baseAddress { - self.pointee.next_out = baseAddress - self.pointee.avail_out = UInt32(buffer.count) - } else { - self.pointee.next_out = nil - self.pointee.avail_out = 0 - } - } - - func setNextOutputBuffer(_ buffer: UnsafeMutableRawBufferPointer?) { - if let buffer = buffer, let baseAddress = buffer.baseAddress { - self.pointee.next_out = CGRPCZlib_castVoidToBytefPointer(baseAddress) - self.pointee.avail_out = UInt32(buffer.count) - } else { - self.pointee.next_out = nil - self.pointee.avail_out = 0 - } - } - - /// Number of bytes available to read `self.nextInputBuffer`. See also: `z_stream.avail_in`. - var availableInputBytes: Int { - get { - Int(self.pointee.avail_in) - } - set { - self.pointee.avail_in = UInt32(newValue) - } - } - - /// The remaining writable space in `nextOutputBuffer`. See also: `z_stream.avail_out`. - var availableOutputBytes: Int { - get { - Int(self.pointee.avail_out) - } - set { - self.pointee.avail_out = UInt32(newValue) - } - } - - /// The total number of bytes written to the output buffer. See also: `z_stream.total_out`. - var totalOutputBytes: Int { - Int(self.pointee.total_out) - } - - /// The last error message that zlib wrote. No message is guaranteed on error, however, `nil` is - /// guaranteed if there is no error. See also `z_stream.msg`. - var lastError: String? { - self.pointee.msg.map { String(cString: $0) } - } - - func inflate(input: inout ByteBuffer, limit: Int) throws -> [UInt8] { - return try input.readWithUnsafeMutableReadableBytes { inputPointer in - self.setNextInputBuffer(inputPointer) - defer { - self.setNextInputBuffer(nil) - self.setNextOutputBuffer(nil) - } - - // Assume the output will be twice as large as the input. - var output = [UInt8](repeating: 0, count: min(inputPointer.count * 2, limit)) - var offset = 0 - - while true { - let (finished, written) = try output[offset...].withUnsafeMutableBytes { outPointer in - self.setNextOutputBuffer(outPointer) - - let finished: Bool - - // Possible return codes: - // - Z_OK: some progress has been made - // - Z_STREAM_END: the end of the compressed data has been reached and all uncompressed - // output has been produced - // - Z_NEED_DICT: a preset dictionary is needed at this point - // - Z_DATA_ERROR: the input data was corrupted - // - Z_STREAM_ERROR: the stream structure was inconsistent - // - Z_MEM_ERROR there was not enough memory - // - Z_BUF_ERROR if no progress was possible or if there was not enough room in the output - // buffer when Z_FINISH is used. - // - // Note that Z_OK is not okay here since we always flush with Z_FINISH and therefore - // use Z_STREAM_END as our success criteria. - let rc = CGRPCZlib_inflate(self, Z_FINISH) - switch rc { - case Z_STREAM_END: - finished = true - case Z_BUF_ERROR: - finished = false - default: - throw RPCError( - code: .internalError, - message: "Decompression error", - cause: ZlibError(code: Int(rc), message: self.lastError ?? "") - ) - } - - let size = outPointer.count - self.availableOutputBytes - return (finished, size) - } - - if finished { - output.removeLast(output.count - self.totalOutputBytes) - let bytesRead = inputPointer.count - self.availableInputBytes - return (bytesRead, output) - } else { - offset += written - let newSize = min(output.count * 2, limit) - if newSize == output.count { - throw RPCError(code: .resourceExhausted, message: "Message is too large to decompress.") - } else { - output.append(contentsOf: repeatElement(0, count: newSize - output.count)) - } - } - } - } - } - - func deflate( - _ input: [UInt8], - into output: inout ByteBuffer, - upperBound: Int - ) throws(ZlibError) -> Int { - defer { - self.setNextInputBuffer(nil) - self.setNextOutputBuffer(nil) - } - - do { - var input = input - return try input.withUnsafeMutableBytes { input in - self.setNextInputBuffer(input) - - return try output.writeWithUnsafeMutableBytes(minimumWritableBytes: upperBound) { output in - self.setNextOutputBuffer(output) - - let rc = CGRPCZlib_deflate(self, Z_FINISH) - - // Possible return codes: - // - Z_OK: some progress has been made - // - Z_STREAM_END: all input has been consumed and all output has been produced (only when - // flush is set to Z_FINISH) - // - Z_STREAM_ERROR: the stream state was inconsistent - // - Z_BUF_ERROR: no progress is possible - // - // The documentation notes that Z_BUF_ERROR is not fatal, and deflate() can be called again - // with more input and more output space to continue compressing. However, we - // call `deflateBound()` before `deflate()` which guarantees that the output size will not be - // larger than the value returned by `deflateBound()` if `Z_FINISH` flush is used. As such, - // the only acceptable outcome is `Z_STREAM_END`. - guard rc == Z_STREAM_END else { - throw ZlibError(code: Int(rc), message: self.lastError ?? "") - } - - return output.count - self.availableOutputBytes - } - } - } catch let error as ZlibError { - throw error - } catch { - // Shouldn't happen as 'withUnsafeMutableBytes' and 'writeWithUnsafeMutableBytes' are - // marked 'rethrows' (but don't support typed throws, yet) and the closure only throws - // an 'RPCError' which is handled above. - fatalError("Unexpected error of type \(type(of: error))") - } - } -} diff --git a/Sources/GRPCHTTP2Core/GRPCMessageDecoder.swift b/Sources/GRPCHTTP2Core/GRPCMessageDecoder.swift deleted file mode 100644 index 9d557a6bf..000000000 --- a/Sources/GRPCHTTP2Core/GRPCMessageDecoder.swift +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -internal import GRPCCore -package import NIOCore - -/// A ``GRPCMessageDecoder`` helps with the deframing of gRPC data frames: -/// - It reads the frame's metadata to know whether the message payload is compressed or not, and its length -/// - It reads and decompresses the payload, if compressed -/// - It helps put together frames that have been split across multiple `ByteBuffers` by the underlying transport -struct GRPCMessageDecoder: NIOSingleStepByteToMessageDecoder { - /// Length of the gRPC message header (1 compression byte, 4 bytes for the length). - static let metadataLength = 5 - - typealias InboundOut = [UInt8] - - private let decompressor: Zlib.Decompressor? - private let maxPayloadSize: Int - - /// Create a new ``GRPCMessageDeframer``. - /// - Parameters: - /// - maxPayloadSize: The maximum size a message payload can be. - /// - decompressor: A `Zlib.Decompressor` to use when decompressing compressed gRPC messages. - /// - Important: You must call `end()` on the `decompressor` when you're done using it, to clean - /// up any resources allocated by `Zlib`. - init( - maxPayloadSize: Int, - decompressor: Zlib.Decompressor? = nil - ) { - self.maxPayloadSize = maxPayloadSize - self.decompressor = decompressor - } - - mutating func decode(buffer: inout ByteBuffer) throws -> InboundOut? { - guard buffer.readableBytes >= Self.metadataLength else { - // If we cannot read enough bytes to cover the metadata's length, then we - // need to wait for more bytes to become available to us. - return nil - } - - // Store the current reader index in case we don't yet have enough - // bytes in the buffer to decode a full frame, and need to reset it. - // The force-unwraps for the compression flag and message length are safe, - // because we've checked just above that we've got at least enough bytes to - // read all of the metadata. - let originalReaderIndex = buffer.readerIndex - let isMessageCompressed = buffer.readInteger(as: UInt8.self)! == 1 - let messageLength = buffer.readInteger(as: UInt32.self)! - - if messageLength > self.maxPayloadSize { - throw RPCError( - code: .resourceExhausted, - message: """ - Message has exceeded the configured maximum payload size \ - (max: \(self.maxPayloadSize), actual: \(messageLength)) - """ - ) - } - - guard var message = buffer.readSlice(length: Int(messageLength)) else { - // `ByteBuffer/readSlice(length:)` returns nil when there are not enough - // bytes to read the requested length. This can happen if we don't yet have - // enough bytes buffered to read the full message payload. - // By reading the metadata though, we have already moved the reader index, - // so we must reset it to its previous, original position for now, - // and return. We'll try decoding again, once more bytes become available - // in our buffer. - buffer.moveReaderIndex(to: originalReaderIndex) - return nil - } - - if isMessageCompressed { - guard let decompressor = self.decompressor else { - // We cannot decompress the payload - throw an error. - throw RPCError( - code: .internalError, - message: "Received a compressed message payload, but no decompressor has been configured." - ) - } - return try decompressor.decompress(&message, limit: self.maxPayloadSize) - } else { - return Array(buffer: message) - } - } - - mutating func decodeLast(buffer: inout ByteBuffer, seenEOF: Bool) throws -> InboundOut? { - try self.decode(buffer: &buffer) - } -} - -package struct GRPCMessageDeframer { - private var decoder: GRPCMessageDecoder - private var buffer: Optional - - package var _readerIndex: Int? { - self.buffer?.readerIndex - } - - init(maxPayloadSize: Int, decompressor: Zlib.Decompressor?) { - self.decoder = GRPCMessageDecoder( - maxPayloadSize: maxPayloadSize, - decompressor: decompressor - ) - self.buffer = nil - } - - package init(maxPayloadSize: Int) { - self.decoder = GRPCMessageDecoder(maxPayloadSize: maxPayloadSize, decompressor: nil) - self.buffer = nil - } - - package mutating func append(_ buffer: ByteBuffer) { - if self.buffer == nil || self.buffer!.readableBytes == 0 { - self.buffer = buffer - } else { - // Avoid having too many read bytes in the buffer which can lead to the buffer growing much - // larger than is necessary. - let readerIndex = self.buffer!.readerIndex - if readerIndex > 1024 && readerIndex > (self.buffer!.capacity / 2) { - self.buffer!.discardReadBytes() - } - self.buffer!.writeImmutableBuffer(buffer) - } - } - - package mutating func decodeNext() throws -> [UInt8]? { - guard (self.buffer?.readableBytes ?? 0) > 0 else { return nil } - // Above checks mean this is both non-nil and non-empty. - let message = try self.decoder.decode(buffer: &self.buffer!) - return message - } -} - -extension GRPCMessageDeframer { - mutating func decode(into queue: inout OneOrManyQueue<[UInt8]>) throws { - while let next = try self.decodeNext() { - queue.append(next) - } - } -} diff --git a/Sources/GRPCHTTP2Core/GRPCMessageFramer.swift b/Sources/GRPCHTTP2Core/GRPCMessageFramer.swift deleted file mode 100644 index 509b7ea35..000000000 --- a/Sources/GRPCHTTP2Core/GRPCMessageFramer.swift +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -internal import GRPCCore -internal import NIOCore - -/// A ``GRPCMessageFramer`` helps with the framing of gRPC data frames: -/// - It prepends data with the required metadata (compression flag and message length). -/// - It compresses messages using the specified compression algorithm (if configured). -/// - It coalesces multiple messages (appended into the `Framer` by calling ``append(_:compress:)``) -/// into a single `ByteBuffer`. -struct GRPCMessageFramer { - /// Length of the gRPC message header (1 compression byte, 4 bytes for the length). - static let metadataLength = 5 - - /// Maximum size the `writeBuffer` can be when concatenating multiple frames. - /// This limit will not be considered if only a single message/frame is written into the buffer, meaning - /// frames with messages over 64KB can still be written. - /// - Note: This is expressed as the power of 2 closer to 64KB (i.e., 64KiB) because `ByteBuffer` - /// reserves capacity in powers of 2. This way, we can take advantage of the whole buffer. - static let maxWriteBufferLength = 65_536 - - private var pendingMessages: OneOrManyQueue<(bytes: [UInt8], promise: EventLoopPromise?)> - - private var writeBuffer: ByteBuffer - - /// Create a new ``GRPCMessageFramer``. - init() { - self.pendingMessages = OneOrManyQueue() - self.writeBuffer = ByteBuffer() - } - - /// Queue the given bytes to be framed and potentially coalesced alongside other messages in a `ByteBuffer`. - /// The resulting data will be returned when calling ``GRPCMessageFramer/next()``. - mutating func append(_ bytes: [UInt8], promise: EventLoopPromise?) { - self.pendingMessages.append((bytes, promise)) - } - - /// If there are pending messages to be framed, a `ByteBuffer` will be returned with the framed data. - /// Data may also be compressed (if configured) and multiple frames may be coalesced into the same `ByteBuffer`. - /// - Parameter compressor: An optional compressor: if present, payloads will be compressed; otherwise - /// they'll be framed as-is. - /// - Throws: If an error is encountered, such as a compression failure, an error will be thrown. - mutating func nextResult( - compressor: Zlib.Compressor? = nil - ) -> (result: Result, promise: EventLoopPromise?)? { - if self.pendingMessages.isEmpty { - // Nothing pending: exit early. - return nil - } - - defer { - // To avoid holding an excessively large buffer, if its size is larger than - // our threshold (`maxWriteBufferLength`), then reset it to a new `ByteBuffer`. - if self.writeBuffer.capacity > Self.maxWriteBufferLength { - self.writeBuffer = ByteBuffer() - } - } - - var requiredCapacity = 0 - for message in self.pendingMessages { - requiredCapacity += message.bytes.count + Self.metadataLength - } - self.writeBuffer.clear(minimumCapacity: requiredCapacity) - - var pendingWritePromise: EventLoopPromise? - while let message = self.pendingMessages.pop() { - pendingWritePromise.setOrCascade(to: message.promise) - - do { - try self.encode(message.bytes, compressor: compressor) - } catch let rpcError { - return (result: .failure(rpcError), promise: pendingWritePromise) - } - } - - return (result: .success(self.writeBuffer), promise: pendingWritePromise) - } - - private mutating func encode(_ message: [UInt8], compressor: Zlib.Compressor?) throws(RPCError) { - if let compressor { - self.writeBuffer.writeInteger(UInt8(1)) // Set compression flag - - // Write zeroes as length - we'll write the actual compressed size after compression. - let lengthIndex = self.writeBuffer.writerIndex - self.writeBuffer.writeInteger(UInt32(0)) - - // Compress and overwrite the payload length field with the right length. - do { - let writtenBytes = try compressor.compress(message, into: &self.writeBuffer) - self.writeBuffer.setInteger(UInt32(writtenBytes), at: lengthIndex) - } catch let zlibError { - throw RPCError(code: .internalError, message: "Compression failed", cause: zlibError) - } - } else { - self.writeBuffer.writeMultipleIntegers( - UInt8(0), // Clear compression flag - UInt32(message.count) // Set message length - ) - self.writeBuffer.writeBytes(message) - } - } -} diff --git a/Sources/GRPCHTTP2Core/GRPCStreamStateMachine.swift b/Sources/GRPCHTTP2Core/GRPCStreamStateMachine.swift deleted file mode 100644 index a040a4524..000000000 --- a/Sources/GRPCHTTP2Core/GRPCStreamStateMachine.swift +++ /dev/null @@ -1,1928 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -internal import GRPCCore -internal import NIOCore -internal import NIOHPACK -internal import NIOHTTP1 - -package enum Scheme: String { - case http - case https -} - -enum GRPCStreamStateMachineConfiguration { - case client(ClientConfiguration) - case server(ServerConfiguration) - - struct ClientConfiguration { - var methodDescriptor: MethodDescriptor - var scheme: Scheme - var outboundEncoding: CompressionAlgorithm - var acceptedEncodings: CompressionAlgorithmSet - - init( - methodDescriptor: MethodDescriptor, - scheme: Scheme, - outboundEncoding: CompressionAlgorithm, - acceptedEncodings: CompressionAlgorithmSet - ) { - self.methodDescriptor = methodDescriptor - self.scheme = scheme - self.outboundEncoding = outboundEncoding - self.acceptedEncodings = acceptedEncodings.union(.none) - } - } - - struct ServerConfiguration { - var scheme: Scheme - var acceptedEncodings: CompressionAlgorithmSet - - init(scheme: Scheme, acceptedEncodings: CompressionAlgorithmSet) { - self.scheme = scheme - self.acceptedEncodings = acceptedEncodings.union(.none) - } - } -} - -private enum GRPCStreamStateMachineState { - case clientIdleServerIdle(ClientIdleServerIdleState) - case clientOpenServerIdle(ClientOpenServerIdleState) - case clientOpenServerOpen(ClientOpenServerOpenState) - case clientOpenServerClosed(ClientOpenServerClosedState) - case clientClosedServerIdle(ClientClosedServerIdleState) - case clientClosedServerOpen(ClientClosedServerOpenState) - case clientClosedServerClosed(ClientClosedServerClosedState) - case _modifying - - struct ClientIdleServerIdleState { - let maxPayloadSize: Int - } - - struct ClientOpenServerIdleState { - let maxPayloadSize: Int - var framer: GRPCMessageFramer - var compressor: Zlib.Compressor? - var outboundCompression: CompressionAlgorithm - - // The deframer must be optional because the client will not have one configured - // until the server opens and sends a grpc-encoding header. - // It will be present for the server though, because even though it's idle, - // it can still receive compressed messages from the client. - var deframer: GRPCMessageDeframer? - var decompressor: Zlib.Decompressor? - - var inboundMessageBuffer: OneOrManyQueue<[UInt8]> - - // Store the headers received from the remote peer, its storage can be reused when sending - // headers back to the remote peer. - var headers: HPACKHeaders - - init( - previousState: ClientIdleServerIdleState, - compressor: Zlib.Compressor?, - outboundCompression: CompressionAlgorithm, - framer: GRPCMessageFramer, - decompressor: Zlib.Decompressor?, - deframer: GRPCMessageDeframer?, - headers: HPACKHeaders - ) { - self.maxPayloadSize = previousState.maxPayloadSize - self.compressor = compressor - self.outboundCompression = outboundCompression - self.framer = framer - self.decompressor = decompressor - self.deframer = deframer - self.inboundMessageBuffer = .init() - self.headers = headers - } - } - - struct ClientOpenServerOpenState { - var framer: GRPCMessageFramer - var compressor: Zlib.Compressor? - var outboundCompression: CompressionAlgorithm - - var deframer: GRPCMessageDeframer - var decompressor: Zlib.Decompressor? - - var inboundMessageBuffer: OneOrManyQueue<[UInt8]> - - // Store the headers received from the remote peer, its storage can be reused when sending - // headers back to the remote peer. - var headers: HPACKHeaders - - init( - previousState: ClientOpenServerIdleState, - deframer: GRPCMessageDeframer, - decompressor: Zlib.Decompressor? - ) { - self.framer = previousState.framer - self.compressor = previousState.compressor - self.outboundCompression = previousState.outboundCompression - - self.deframer = deframer - self.decompressor = decompressor - - self.inboundMessageBuffer = previousState.inboundMessageBuffer - self.headers = previousState.headers - } - } - - struct ClientOpenServerClosedState { - var framer: GRPCMessageFramer? - var compressor: Zlib.Compressor? - var outboundCompression: CompressionAlgorithm - - let deframer: GRPCMessageDeframer? - var decompressor: Zlib.Decompressor? - - var inboundMessageBuffer: OneOrManyQueue<[UInt8]> - - // This transition should only happen on the server-side when, upon receiving - // initial client metadata, some of the headers are invalid and we must reject - // the RPC. - // We will mark the client as open (because it sent initial metadata albeit - // invalid) but we'll close the server, meaning all future messages sent from - // the client will be ignored. Because of this, we won't need to frame or - // deframe any messages, as we won't be reading or writing any messages. - init(previousState: ClientIdleServerIdleState) { - self.framer = nil - self.compressor = nil - self.outboundCompression = .none - self.deframer = nil - self.decompressor = nil - self.inboundMessageBuffer = .init() - } - - init(previousState: ClientOpenServerOpenState) { - self.framer = previousState.framer - self.compressor = previousState.compressor - self.outboundCompression = previousState.outboundCompression - self.deframer = previousState.deframer - self.decompressor = previousState.decompressor - self.inboundMessageBuffer = previousState.inboundMessageBuffer - } - - init(previousState: ClientOpenServerIdleState) { - self.framer = previousState.framer - self.compressor = previousState.compressor - self.outboundCompression = previousState.outboundCompression - self.inboundMessageBuffer = previousState.inboundMessageBuffer - // The server went directly from idle to closed - this means it sent a - // trailers-only response: - // - if we're the client, the previous state was a nil deframer, but that - // is okay because we don't need a deframer as the server won't be sending - // any messages; - // - if we're the server, we'll keep whatever deframer we had. - self.deframer = previousState.deframer - self.decompressor = previousState.decompressor - } - } - - struct ClientClosedServerIdleState { - let maxPayloadSize: Int - var framer: GRPCMessageFramer - var compressor: Zlib.Compressor? - var outboundCompression: CompressionAlgorithm - - let deframer: GRPCMessageDeframer? - var decompressor: Zlib.Decompressor? - - var inboundMessageBuffer: OneOrManyQueue<[UInt8]> - - // Store the headers received from the remote peer, its storage can be reused when sending - // headers back to the remote peer. - var headers: HPACKHeaders - - /// This transition should only happen on the client-side. - /// It can happen if the request times out before the client outbound can be opened, or if the stream is - /// unexpectedly closed for some other reason on the client before it can transition to open. - init(previousState: ClientIdleServerIdleState) { - self.maxPayloadSize = previousState.maxPayloadSize - // We don't need a compressor since we won't be sending any messages. - self.framer = GRPCMessageFramer() - self.compressor = nil - self.outboundCompression = .none - - // We haven't received anything from the server. - self.deframer = nil - self.decompressor = nil - - self.inboundMessageBuffer = .init() - self.headers = [:] - } - - /// This transition should only happen on the server-side. - /// We are closing the client as soon as it opens (i.e., endStream was set when receiving the client's - /// initial metadata). We don't need to know a decompression algorithm, since we won't receive - /// any more messages from the client anyways, as it's closed. - init( - previousState: ClientIdleServerIdleState, - compressionAlgorithm: CompressionAlgorithm, - headers: HPACKHeaders - ) { - self.maxPayloadSize = previousState.maxPayloadSize - - if let zlibMethod = Zlib.Method(encoding: compressionAlgorithm) { - self.compressor = Zlib.Compressor(method: zlibMethod) - self.outboundCompression = compressionAlgorithm - } else { - self.compressor = nil - self.outboundCompression = .none - } - self.framer = GRPCMessageFramer() - // We don't need a deframer since we won't receive any messages from the - // client: it's closed. - self.deframer = nil - self.inboundMessageBuffer = .init() - self.headers = headers - } - - init(previousState: ClientOpenServerIdleState) { - self.maxPayloadSize = previousState.maxPayloadSize - self.framer = previousState.framer - self.compressor = previousState.compressor - self.outboundCompression = previousState.outboundCompression - self.deframer = previousState.deframer - self.decompressor = previousState.decompressor - self.inboundMessageBuffer = previousState.inboundMessageBuffer - self.headers = previousState.headers - } - } - - struct ClientClosedServerOpenState { - var framer: GRPCMessageFramer - var compressor: Zlib.Compressor? - var outboundCompression: CompressionAlgorithm - - var deframer: GRPCMessageDeframer? - var decompressor: Zlib.Decompressor? - - var inboundMessageBuffer: OneOrManyQueue<[UInt8]> - - // Store the headers received from the remote peer, its storage can be reused when sending - // headers back to the remote peer. - var headers: HPACKHeaders - - init(previousState: ClientOpenServerOpenState) { - self.framer = previousState.framer - self.compressor = previousState.compressor - self.outboundCompression = previousState.outboundCompression - self.deframer = previousState.deframer - self.decompressor = previousState.decompressor - self.inboundMessageBuffer = previousState.inboundMessageBuffer - self.headers = previousState.headers - } - - /// This should be called from the server path, as the deframer will already be configured in this scenario. - init(previousState: ClientClosedServerIdleState) { - self.framer = previousState.framer - self.compressor = previousState.compressor - self.outboundCompression = previousState.outboundCompression - - // In the case of the server, we don't need to deframe/decompress any more - // messages, since the client's closed. - self.deframer = nil - self.decompressor = nil - - self.inboundMessageBuffer = previousState.inboundMessageBuffer - self.headers = previousState.headers - } - - /// This should only be called from the client path, as the deframer has not yet been set up. - init( - previousState: ClientClosedServerIdleState, - decompressionAlgorithm: CompressionAlgorithm - ) { - self.framer = previousState.framer - self.compressor = previousState.compressor - self.outboundCompression = previousState.outboundCompression - - // In the case of the client, it will only be able to set up the deframer - // after it receives the chosen encoding from the server. - if let zlibMethod = Zlib.Method(encoding: decompressionAlgorithm) { - self.decompressor = Zlib.Decompressor(method: zlibMethod) - } - - self.deframer = GRPCMessageDeframer( - maxPayloadSize: previousState.maxPayloadSize, - decompressor: self.decompressor - ) - - self.inboundMessageBuffer = previousState.inboundMessageBuffer - self.headers = previousState.headers - } - } - - struct ClientClosedServerClosedState { - // We still need the framer and compressor in case the server has closed - // but its buffer is not yet empty and still needs to send messages out to - // the client. - var framer: GRPCMessageFramer? - var compressor: Zlib.Compressor? - var outboundCompression: CompressionAlgorithm - - // These are already deframed, so we don't need the deframer anymore. - var inboundMessageBuffer: OneOrManyQueue<[UInt8]> - - // This transition should only happen on the server-side when, upon receiving - // initial client metadata, some of the headers are invalid and we must reject - // the RPC. - // We will mark the client as closed (because it set the EOS flag, even if - // the initial metadata was invalid) and we'll close the server too. - // Because of this, we won't need to frame any messages, as we - // won't be writing any messages. - init(previousState: ClientIdleServerIdleState) { - self.framer = nil - self.compressor = nil - self.outboundCompression = .none - self.inboundMessageBuffer = .init() - } - - init(previousState: ClientClosedServerOpenState) { - self.framer = previousState.framer - self.compressor = previousState.compressor - self.outboundCompression = previousState.outboundCompression - self.inboundMessageBuffer = previousState.inboundMessageBuffer - } - - init(previousState: ClientClosedServerIdleState) { - self.framer = previousState.framer - self.compressor = previousState.compressor - self.outboundCompression = previousState.outboundCompression - self.inboundMessageBuffer = previousState.inboundMessageBuffer - } - - init(previousState: ClientOpenServerIdleState) { - self.framer = previousState.framer - self.compressor = previousState.compressor - self.outboundCompression = previousState.outboundCompression - self.inboundMessageBuffer = previousState.inboundMessageBuffer - } - - init(previousState: ClientOpenServerOpenState) { - self.framer = previousState.framer - self.compressor = previousState.compressor - self.outboundCompression = previousState.outboundCompression - self.inboundMessageBuffer = previousState.inboundMessageBuffer - } - - init(previousState: ClientOpenServerClosedState) { - self.framer = previousState.framer - self.compressor = previousState.compressor - self.outboundCompression = previousState.outboundCompression - self.inboundMessageBuffer = previousState.inboundMessageBuffer - } - } -} - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -struct GRPCStreamStateMachine { - private var state: GRPCStreamStateMachineState - private var configuration: GRPCStreamStateMachineConfiguration - private var skipAssertions: Bool - - struct InvalidState: Error { - var message: String - init(_ message: String) { - self.message = message - } - } - - init( - configuration: GRPCStreamStateMachineConfiguration, - maxPayloadSize: Int, - skipAssertions: Bool = false - ) { - self.state = .clientIdleServerIdle(.init(maxPayloadSize: maxPayloadSize)) - self.configuration = configuration - self.skipAssertions = skipAssertions - } - - mutating func send(metadata: Metadata) throws(InvalidState) -> HPACKHeaders { - switch self.configuration { - case .client(let clientConfiguration): - return try self.clientSend(metadata: metadata, configuration: clientConfiguration) - case .server(let serverConfiguration): - return try self.serverSend(metadata: metadata, configuration: serverConfiguration) - } - } - - mutating func send(message: [UInt8], promise: EventLoopPromise?) throws(InvalidState) { - switch self.configuration { - case .client: - try self.clientSend(message: message, promise: promise) - case .server: - try self.serverSend(message: message, promise: promise) - } - } - - mutating func closeOutbound() throws(InvalidState) { - switch self.configuration { - case .client: - try self.clientCloseOutbound() - case .server: - try self.invalidState("Server cannot call close: it must send status and trailers.") - } - } - - mutating func send( - status: Status, - metadata: Metadata - ) throws(InvalidState) -> HPACKHeaders { - switch self.configuration { - case .client: - try self.invalidState( - "Client cannot send status and trailer." - ) - case .server: - return try self.serverSend( - status: status, - customMetadata: metadata - ) - } - } - - enum OnMetadataReceived: Equatable { - case receivedMetadata(Metadata, MethodDescriptor?) - case doNothing - - // Client-specific actions - case receivedStatusAndMetadata_clientOnly(status: Status, metadata: Metadata) - - // Server-specific actions - case rejectRPC_serverOnly(trailers: HPACKHeaders) - case protocolViolation_serverOnly - } - - mutating func receive( - headers: HPACKHeaders, - endStream: Bool - ) throws(InvalidState) -> OnMetadataReceived { - switch self.configuration { - case .client(let clientConfiguration): - return try self.clientReceive( - headers: headers, - endStream: endStream, - configuration: clientConfiguration - ) - case .server(let serverConfiguration): - return try self.serverReceive( - headers: headers, - endStream: endStream, - configuration: serverConfiguration - ) - } - } - - enum OnBufferReceivedAction: Equatable { - case readInbound - case doNothing - - // This will be returned when the server sends a data frame with EOS set. - // This is invalid as per the protocol specification, because the server - // can only close by sending trailers, not by setting EOS when sending - // a message. - case endRPCAndForwardErrorStatus_clientOnly(Status) - - case forwardErrorAndClose_serverOnly(RPCError) - } - - mutating func receive( - buffer: ByteBuffer, - endStream: Bool - ) throws(InvalidState) -> OnBufferReceivedAction { - switch self.configuration { - case .client: - return try self.clientReceive(buffer: buffer, endStream: endStream) - case .server: - return try self.serverReceive(buffer: buffer, endStream: endStream) - } - } - - /// The result of requesting the next outbound frame, which may contain multiple messages. - enum OnNextOutboundFrame { - /// Either the receiving party is closed, so we shouldn't send any more frames; or the sender is done - /// writing messages (i.e. we are now closed). - case noMoreMessages - /// There isn't a frame ready to be sent, but we could still receive more messages, so keep trying. - case awaitMoreMessages - /// A frame is ready to be sent. - case sendFrame( - frame: ByteBuffer, - promise: EventLoopPromise? - ) - case closeAndFailPromise(EventLoopPromise?, RPCError) - - init(result: Result, promise: EventLoopPromise?) { - switch result { - case .success(let buffer): - self = .sendFrame(frame: buffer, promise: promise) - case .failure(let error): - self = .closeAndFailPromise(promise, error) - } - } - } - - mutating func nextOutboundFrame() throws(InvalidState) -> OnNextOutboundFrame { - switch self.configuration { - case .client: - return try self.clientNextOutboundFrame() - case .server: - return try self.serverNextOutboundFrame() - } - } - - /// The result of requesting the next inbound message. - enum OnNextInboundMessage: Equatable { - /// The sender is done writing messages and there are no more messages to be received. - case noMoreMessages - /// There isn't a message ready to be sent, but we could still receive more, so keep trying. - case awaitMoreMessages - /// A message has been received. - case receiveMessage([UInt8]) - } - - mutating func nextInboundMessage() -> OnNextInboundMessage { - switch self.configuration { - case .client: - return self.clientNextInboundMessage() - case .server: - return self.serverNextInboundMessage() - } - } - - mutating func tearDown() { - switch self.state { - case .clientIdleServerIdle: - () - case .clientOpenServerIdle(let state): - state.compressor?.end() - state.decompressor?.end() - case .clientOpenServerOpen(let state): - state.compressor?.end() - state.decompressor?.end() - case .clientOpenServerClosed(let state): - state.compressor?.end() - state.decompressor?.end() - case .clientClosedServerIdle(let state): - state.compressor?.end() - state.decompressor?.end() - case .clientClosedServerOpen(let state): - state.compressor?.end() - state.decompressor?.end() - case .clientClosedServerClosed(let state): - state.compressor?.end() - case ._modifying: - preconditionFailure() - } - } - - enum OnUnexpectedInboundClose { - case forwardStatus_clientOnly(Status) - case fireError_serverOnly(any Error) - case doNothing - - init(serverCloseReason: UnexpectedInboundCloseReason) { - switch serverCloseReason { - case .streamReset, .channelInactive: - self = .fireError_serverOnly(RPCError(serverCloseReason)) - case .errorThrown(let error): - self = .fireError_serverOnly(error) - } - } - } - - enum UnexpectedInboundCloseReason { - case streamReset - case channelInactive - case errorThrown(any Error) - } - - mutating func unexpectedInboundClose( - reason: UnexpectedInboundCloseReason - ) -> OnUnexpectedInboundClose { - switch self.configuration { - case .client: - return self.clientUnexpectedInboundClose(reason: reason) - case .server: - return self.serverUnexpectedInboundClose(reason: reason) - } - } -} - -// - MARK: Client - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -extension GRPCStreamStateMachine { - private func makeClientHeaders( - methodDescriptor: MethodDescriptor, - scheme: Scheme, - outboundEncoding: CompressionAlgorithm?, - acceptedEncodings: CompressionAlgorithmSet, - customMetadata: Metadata - ) -> HPACKHeaders { - var headers = HPACKHeaders() - headers.reserveCapacity(7 + customMetadata.count) - - // Add required headers. - // See https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests - - // The order is important here: reserved HTTP2 headers (those starting with `:`) - // must come before all other headers. - headers.add("POST", forKey: .method) - headers.add(scheme.rawValue, forKey: .scheme) - headers.add(methodDescriptor.path, forKey: .path) - - // Add required gRPC headers. - headers.add(ContentType.grpc.canonicalValue, forKey: .contentType) - headers.add("trailers", forKey: .te) // Used to detect incompatible proxies - - if let encoding = outboundEncoding, encoding != .none { - headers.add(encoding.name, forKey: .encoding) - } - - for encoding in acceptedEncodings.elements.filter({ $0 != .none }) { - headers.add(encoding.name, forKey: .acceptEncoding) - } - - for metadataPair in customMetadata { - headers.add(name: metadataPair.key, value: metadataPair.value.encoded()) - } - - return headers - } - - private mutating func clientSend( - metadata: Metadata, - configuration: GRPCStreamStateMachineConfiguration.ClientConfiguration - ) throws(InvalidState) -> HPACKHeaders { - // Client sends metadata only when opening the stream. - switch self.state { - case .clientIdleServerIdle(let state): - let outboundEncoding = configuration.outboundEncoding - let compressor = Zlib.Method(encoding: outboundEncoding) - .flatMap { Zlib.Compressor(method: $0) } - self.state = .clientOpenServerIdle( - .init( - previousState: state, - compressor: compressor, - outboundCompression: outboundEncoding, - framer: GRPCMessageFramer(), - decompressor: nil, - deframer: nil, - headers: [:] - ) - ) - return self.makeClientHeaders( - methodDescriptor: configuration.methodDescriptor, - scheme: configuration.scheme, - outboundEncoding: configuration.outboundEncoding, - acceptedEncodings: configuration.acceptedEncodings, - customMetadata: metadata - ) - case .clientOpenServerIdle, .clientOpenServerOpen, .clientOpenServerClosed: - try self.invalidState( - "Client is already open: shouldn't be sending metadata." - ) - case .clientClosedServerIdle, .clientClosedServerOpen, .clientClosedServerClosed: - try self.invalidState( - "Client is closed: can't send metadata." - ) - case ._modifying: - preconditionFailure() - } - } - - private mutating func clientSend( - message: [UInt8], - promise: EventLoopPromise? - ) throws(InvalidState) { - switch self.state { - case .clientIdleServerIdle: - try self.invalidState("Client not yet open.") - - case .clientOpenServerIdle(var state): - self.state = ._modifying - state.framer.append(message, promise: promise) - self.state = .clientOpenServerIdle(state) - - case .clientOpenServerOpen(var state): - self.state = ._modifying - state.framer.append(message, promise: promise) - self.state = .clientOpenServerOpen(state) - - case .clientOpenServerClosed: - // The server has closed, so it makes no sense to send the rest of the request. - () - - case .clientClosedServerIdle, .clientClosedServerOpen, .clientClosedServerClosed: - try self.invalidState( - "Client is closed, cannot send a message." - ) - - case ._modifying: - preconditionFailure() - } - } - - private mutating func clientCloseOutbound() throws(InvalidState) { - switch self.state { - case .clientIdleServerIdle(let state): - self.state = .clientClosedServerIdle(.init(previousState: state)) - case .clientOpenServerIdle(let state): - self.state = .clientClosedServerIdle(.init(previousState: state)) - case .clientOpenServerOpen(let state): - self.state = .clientClosedServerOpen(.init(previousState: state)) - case .clientOpenServerClosed(let state): - self.state = .clientClosedServerClosed(.init(previousState: state)) - case .clientClosedServerIdle, .clientClosedServerOpen, .clientClosedServerClosed: - // Client is already closed - nothing to do. - () - case ._modifying: - preconditionFailure() - } - } - - /// Returns the client's next request to the server. - /// - Returns: The request to be made to the server. - private mutating func clientNextOutboundFrame() throws(InvalidState) -> OnNextOutboundFrame { - - switch self.state { - case .clientIdleServerIdle: - try self.invalidState("Client is not open yet.") - - case .clientOpenServerIdle(var state): - self.state = ._modifying - let next = state.framer.nextResult(compressor: state.compressor) - self.state = .clientOpenServerIdle(state) - - if let next = next { - return OnNextOutboundFrame(result: next.result, promise: next.promise) - } else { - return .awaitMoreMessages - } - - case .clientOpenServerOpen(var state): - self.state = ._modifying - let next = state.framer.nextResult(compressor: state.compressor) - self.state = .clientOpenServerOpen(state) - - if let next = next { - return OnNextOutboundFrame(result: next.result, promise: next.promise) - } else { - return .awaitMoreMessages - } - - case .clientClosedServerIdle(var state): - self.state = ._modifying - let next = state.framer.nextResult(compressor: state.compressor) - self.state = .clientClosedServerIdle(state) - - if let next = next { - return OnNextOutboundFrame(result: next.result, promise: next.promise) - } else { - return .noMoreMessages - } - - case .clientClosedServerOpen(var state): - self.state = ._modifying - let next = state.framer.nextResult(compressor: state.compressor) - self.state = .clientClosedServerOpen(state) - - if let next = next { - return OnNextOutboundFrame(result: next.result, promise: next.promise) - } else { - return .noMoreMessages - } - - case .clientOpenServerClosed, .clientClosedServerClosed: - // No point in sending any more requests if the server is closed. - return .noMoreMessages - - case ._modifying: - preconditionFailure() - } - } - - private enum ServerHeadersValidationResult { - case valid - case invalid(OnMetadataReceived) - } - - private mutating func clientValidateHeadersReceivedFromServer( - _ metadata: HPACKHeaders - ) -> ServerHeadersValidationResult { - var httpStatus: String? { - metadata.firstString(forKey: .status) - } - var grpcStatus: Status.Code? { - metadata.firstString(forKey: .grpcStatus) - .flatMap { Int($0) } - .flatMap { Status.Code(rawValue: $0) } - } - guard httpStatus == "200" || grpcStatus != nil else { - let httpStatusCode = - httpStatus - .flatMap { Int($0) } - .map { HTTPResponseStatus(statusCode: $0) } - - guard let httpStatusCode else { - return .invalid( - .receivedStatusAndMetadata_clientOnly( - status: .init(code: .unknown, message: "HTTP Status Code is missing."), - metadata: Metadata(headers: metadata) - ) - ) - } - - if (100 ... 199).contains(httpStatusCode.code) { - // For 1xx status codes, the entire header should be skipped and a - // subsequent header should be read. - // See https://github.com/grpc/grpc/blob/master/doc/http-grpc-status-mapping.md - return .invalid(.doNothing) - } - - // Forward the mapped status code. - return .invalid( - .receivedStatusAndMetadata_clientOnly( - status: .init( - code: Status.Code(httpStatusCode: httpStatusCode), - message: "Unexpected non-200 HTTP Status Code." - ), - metadata: Metadata(headers: metadata) - ) - ) - } - - let contentTypeHeader = metadata.first(name: GRPCHTTP2Keys.contentType.rawValue) - guard contentTypeHeader.flatMap(ContentType.init) != nil else { - return .invalid( - .receivedStatusAndMetadata_clientOnly( - status: .init( - code: .internalError, - message: "Missing \(GRPCHTTP2Keys.contentType.rawValue) header" - ), - metadata: Metadata(headers: metadata) - ) - ) - } - - return .valid - } - - private enum ProcessInboundEncodingResult { - case error(OnMetadataReceived) - case success(CompressionAlgorithm) - } - - private func processInboundEncoding( - headers: HPACKHeaders, - configuration: GRPCStreamStateMachineConfiguration.ClientConfiguration - ) -> ProcessInboundEncodingResult { - let inboundEncoding: CompressionAlgorithm - if let serverEncoding = headers.first(name: GRPCHTTP2Keys.encoding.rawValue) { - guard let parsedEncoding = CompressionAlgorithm(name: serverEncoding), - configuration.acceptedEncodings.contains(parsedEncoding) - else { - return .error( - .receivedStatusAndMetadata_clientOnly( - status: .init( - code: .internalError, - message: - "The server picked a compression algorithm ('\(serverEncoding)') the client does not know about." - ), - metadata: Metadata(headers: headers) - ) - ) - } - inboundEncoding = parsedEncoding - } else { - inboundEncoding = .none - } - return .success(inboundEncoding) - } - - private func validateTrailers( - _ trailers: HPACKHeaders - ) throws(InvalidState) -> OnMetadataReceived { - let statusValue = trailers.firstString(forKey: .grpcStatus) - let statusCode = statusValue.flatMap { - Int($0) - }.flatMap { - Status.Code(rawValue: $0) - } - - let status: Status - if let code = statusCode { - let messageFieldValue = trailers.firstString(forKey: .grpcStatusMessage, canonicalForm: false) - let message = messageFieldValue.map { GRPCStatusMessageMarshaller.unmarshall($0) } ?? "" - status = Status(code: code, message: message) - } else { - let message: String - if let statusValue = statusValue { - message = "Invalid 'grpc-status' in trailers (\(statusValue))" - } else { - message = "No 'grpc-status' value in trailers" - } - status = Status(code: .unknown, message: message) - } - - var convertedMetadata = Metadata(headers: trailers) - convertedMetadata.removeAllValues(forKey: GRPCHTTP2Keys.grpcStatus.rawValue) - convertedMetadata.removeAllValues(forKey: GRPCHTTP2Keys.grpcStatusMessage.rawValue) - - return .receivedStatusAndMetadata_clientOnly(status: status, metadata: convertedMetadata) - } - - private mutating func clientReceive( - headers: HPACKHeaders, - endStream: Bool, - configuration: GRPCStreamStateMachineConfiguration.ClientConfiguration - ) throws(InvalidState) -> OnMetadataReceived { - switch self.state { - case .clientOpenServerIdle(let state): - switch (self.clientValidateHeadersReceivedFromServer(headers), endStream) { - case (.invalid(let action), true): - // The headers are invalid, but the server signalled that it was - // closing the stream, so close both client and server. - self.state = .clientClosedServerClosed(.init(previousState: state)) - return action - case (.invalid(let action), false): - self.state = .clientClosedServerIdle(.init(previousState: state)) - return action - case (.valid, true): - // This is a trailers-only response: close server. - self.state = .clientOpenServerClosed(.init(previousState: state)) - return try self.validateTrailers(headers) - case (.valid, false): - switch self.processInboundEncoding(headers: headers, configuration: configuration) { - case .error(let failure): - return failure - case .success(let inboundEncoding): - let decompressor = Zlib.Method(encoding: inboundEncoding) - .flatMap { Zlib.Decompressor(method: $0) } - - self.state = .clientOpenServerOpen( - .init( - previousState: state, - deframer: GRPCMessageDeframer( - maxPayloadSize: state.maxPayloadSize, - decompressor: decompressor - ), - decompressor: decompressor - ) - ) - return .receivedMetadata(Metadata(headers: headers), nil) - } - } - - case .clientOpenServerOpen(let state): - // This state is valid even if endStream is not set: server can send - // trailing metadata without END_STREAM set, and follow it with an - // empty message frame where it is set. - // However, we must make sure that grpc-status is set, otherwise this - // is an invalid state. - if endStream { - self.state = .clientOpenServerClosed(.init(previousState: state)) - } - return try self.validateTrailers(headers) - - case .clientClosedServerIdle(let state): - switch (self.clientValidateHeadersReceivedFromServer(headers), endStream) { - case (.invalid(let action), true): - // The headers are invalid, but the server signalled that it was - // closing the stream, so close the server side too. - self.state = .clientClosedServerClosed(.init(previousState: state)) - return action - case (.invalid(let action), false): - // Client is already closed, so we don't need to update our state. - return action - case (.valid, true): - // This is a trailers-only response: close server. - self.state = .clientClosedServerClosed(.init(previousState: state)) - return try self.validateTrailers(headers) - case (.valid, false): - switch self.processInboundEncoding(headers: headers, configuration: configuration) { - case .error(let failure): - return failure - case .success(let inboundEncoding): - self.state = .clientClosedServerOpen( - .init( - previousState: state, - decompressionAlgorithm: inboundEncoding - ) - ) - return .receivedMetadata(Metadata(headers: headers), nil) - } - } - - case .clientClosedServerOpen(let state): - // This state is valid even if endStream is not set: server can send - // trailing metadata without END_STREAM set, and follow it with an - // empty message frame where it is set. - // However, we must make sure that grpc-status is set, otherwise this - // is an invalid state. - if endStream { - self.state = .clientClosedServerClosed(.init(previousState: state)) - } - return try self.validateTrailers(headers) - - case .clientClosedServerClosed: - // We could end up here if we received a grpc-status header in a previous - // frame (which would have already close the server) and then we receive - // an empty frame with EOS set. - // We wouldn't want to throw in that scenario, so we just ignore it. - // Note that we don't want to ignore it if EOS is not set here though, as - // then it would be an invalid payload. - if !endStream || headers.count > 0 { - try self.invalidState( - "Server is closed, nothing could have been sent." - ) - } - return .doNothing - case .clientIdleServerIdle: - try self.invalidState( - "Server cannot have sent metadata if the client is idle." - ) - case .clientOpenServerClosed: - try self.invalidState( - "Server is closed, nothing could have been sent." - ) - case ._modifying: - preconditionFailure() - } - } - - private mutating func clientReceive( - buffer: ByteBuffer, - endStream: Bool - ) throws(InvalidState) -> OnBufferReceivedAction { - // This is a message received by the client, from the server. - switch self.state { - case .clientIdleServerIdle: - try self.invalidState( - "Cannot have received anything from server if client is not yet open." - ) - - case .clientOpenServerIdle, .clientClosedServerIdle: - try self.invalidState( - "Server cannot have sent a message before sending the initial metadata." - ) - - case .clientOpenServerOpen(var state): - self.state = ._modifying - if endStream { - // This is invalid as per the protocol specification, because the server - // can only close by sending trailers, not by setting EOS when sending - // a message. - self.state = .clientClosedServerClosed(.init(previousState: state)) - return .endRPCAndForwardErrorStatus_clientOnly( - Status( - code: .internalError, - message: """ - Server sent EOS alongside a data frame, but server is only allowed \ - to close by sending status and trailers. - """ - ) - ) - } - - state.deframer.append(buffer) - - do { - try state.deframer.decode(into: &state.inboundMessageBuffer) - self.state = .clientOpenServerOpen(state) - return .readInbound - } catch { - self.state = .clientOpenServerOpen(state) - let status = Status(code: .internalError, message: "Failed to decode message") - return .endRPCAndForwardErrorStatus_clientOnly(status) - } - - case .clientClosedServerOpen(var state): - self.state = ._modifying - if endStream { - self.state = .clientClosedServerClosed(.init(previousState: state)) - return .endRPCAndForwardErrorStatus_clientOnly( - Status( - code: .internalError, - message: """ - Server sent EOS alongside a data frame, but server is only allowed \ - to close by sending status and trailers. - """ - ) - ) - } - - // The client may have sent the end stream and thus it's closed, - // but the server may still be responding. - // The client must have a deframer set up, so force-unwrap is okay. - do { - state.deframer!.append(buffer) - try state.deframer!.decode(into: &state.inboundMessageBuffer) - self.state = .clientClosedServerOpen(state) - return .readInbound - } catch { - self.state = .clientClosedServerOpen(state) - let status = Status(code: .internalError, message: "Failed to decode message") - return .endRPCAndForwardErrorStatus_clientOnly(status) - } - - case .clientOpenServerClosed, .clientClosedServerClosed: - try self.invalidState( - "Cannot have received anything from a closed server." - ) - case ._modifying: - preconditionFailure() - } - } - - private mutating func clientNextInboundMessage() -> OnNextInboundMessage { - switch self.state { - case .clientOpenServerOpen(var state): - self.state = ._modifying - let message = state.inboundMessageBuffer.pop() - self.state = .clientOpenServerOpen(state) - return message.map { .receiveMessage($0) } ?? .awaitMoreMessages - - case .clientOpenServerClosed(var state): - self.state = ._modifying - let message = state.inboundMessageBuffer.pop() - self.state = .clientOpenServerClosed(state) - return message.map { .receiveMessage($0) } ?? .noMoreMessages - - case .clientClosedServerOpen(var state): - self.state = ._modifying - let message = state.inboundMessageBuffer.pop() - self.state = .clientClosedServerOpen(state) - return message.map { .receiveMessage($0) } ?? .awaitMoreMessages - - case .clientClosedServerClosed(var state): - self.state = ._modifying - let message = state.inboundMessageBuffer.pop() - self.state = .clientClosedServerClosed(state) - return message.map { .receiveMessage($0) } ?? .noMoreMessages - - case .clientIdleServerIdle, - .clientOpenServerIdle, - .clientClosedServerIdle: - return .awaitMoreMessages - case ._modifying: - preconditionFailure() - } - } - - private func invalidState(_ message: String, line: UInt = #line) throws(InvalidState) -> Never { - if !self.skipAssertions { - assertionFailure(message, line: line) - } - throw InvalidState(message) - } - - private mutating func clientUnexpectedInboundClose( - reason: UnexpectedInboundCloseReason - ) -> OnUnexpectedInboundClose { - switch self.state { - case .clientIdleServerIdle(let state): - self.state = .clientClosedServerClosed(.init(previousState: state)) - return .forwardStatus_clientOnly(Status(RPCError(reason))) - - case .clientOpenServerIdle(let state): - self.state = .clientClosedServerClosed(.init(previousState: state)) - return .forwardStatus_clientOnly(Status(RPCError(reason))) - - case .clientClosedServerIdle(let state): - self.state = .clientClosedServerClosed(.init(previousState: state)) - return .forwardStatus_clientOnly(Status(RPCError(reason))) - - case .clientOpenServerOpen(let state): - self.state = .clientClosedServerClosed(.init(previousState: state)) - return .forwardStatus_clientOnly(Status(RPCError(reason))) - - case .clientClosedServerOpen(let state): - self.state = .clientClosedServerClosed(.init(previousState: state)) - return .forwardStatus_clientOnly(Status(RPCError(reason))) - - case .clientOpenServerClosed, .clientClosedServerClosed: - return .doNothing - - case ._modifying: - preconditionFailure() - } - } -} - -// - MARK: Server - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -extension GRPCStreamStateMachine { - private func formResponseHeaders( - in headers: inout HPACKHeaders, - outboundEncoding: CompressionAlgorithm?, - configuration: GRPCStreamStateMachineConfiguration.ServerConfiguration, - customMetadata: Metadata - ) { - headers.removeAll(keepingCapacity: true) - - // Response headers always contain :status (HTTP Status 200) and content-type. - // They may also contain grpc-encoding, grpc-accept-encoding, and custom metadata. - headers.reserveCapacity(4 + customMetadata.count) - - headers.add("200", forKey: .status) - headers.add(ContentType.grpc.canonicalValue, forKey: .contentType) - - if let outboundEncoding, outboundEncoding != .none { - headers.add(outboundEncoding.name, forKey: .encoding) - } - - for metadataPair in customMetadata { - headers.add(name: metadataPair.key, value: metadataPair.value.encoded()) - } - } - - private mutating func serverSend( - metadata: Metadata, - configuration: GRPCStreamStateMachineConfiguration.ServerConfiguration - ) throws(InvalidState) -> HPACKHeaders { - // Server sends initial metadata - switch self.state { - case .clientOpenServerIdle(var state): - self.state = ._modifying - let outboundEncoding = state.outboundCompression - self.formResponseHeaders( - in: &state.headers, - outboundEncoding: outboundEncoding, - configuration: configuration, - customMetadata: metadata - ) - - self.state = .clientOpenServerOpen( - .init( - previousState: state, - // In the case of the server, it will already have a deframer set up, - // because it already knows what encoding the client is using: - // it's okay to force-unwrap. - deframer: state.deframer!, - decompressor: state.decompressor - ) - ) - - return state.headers - - case .clientClosedServerIdle(var state): - self.state = ._modifying - let outboundEncoding = state.outboundCompression - self.formResponseHeaders( - in: &state.headers, - outboundEncoding: outboundEncoding, - configuration: configuration, - customMetadata: metadata - ) - self.state = .clientClosedServerOpen(.init(previousState: state)) - return state.headers - - case .clientIdleServerIdle: - try self.invalidState( - "Client cannot be idle if server is sending initial metadata: it must have opened." - ) - case .clientOpenServerClosed, .clientClosedServerClosed: - try self.invalidState( - "Server cannot send metadata if closed." - ) - case .clientOpenServerOpen, .clientClosedServerOpen: - try self.invalidState( - "Server has already sent initial metadata." - ) - case ._modifying: - preconditionFailure() - } - } - - private mutating func serverSend( - message: [UInt8], - promise: EventLoopPromise? - ) throws(InvalidState) { - switch self.state { - case .clientIdleServerIdle, .clientOpenServerIdle, .clientClosedServerIdle: - try self.invalidState( - "Server must have sent initial metadata before sending a message." - ) - - case .clientOpenServerOpen(var state): - self.state = ._modifying - state.framer.append(message, promise: promise) - self.state = .clientOpenServerOpen(state) - - case .clientClosedServerOpen(var state): - self.state = ._modifying - state.framer.append(message, promise: promise) - self.state = .clientClosedServerOpen(state) - - case .clientOpenServerClosed, .clientClosedServerClosed: - try self.invalidState( - "Server can't send a message if it's closed." - ) - case ._modifying: - preconditionFailure() - } - } - - private mutating func serverSend( - status: Status, - customMetadata: Metadata - ) throws(InvalidState) -> HPACKHeaders { - // Close the server. - switch self.state { - case .clientOpenServerOpen(var state): - self.state = ._modifying - state.headers.formTrailers(status: status, metadata: customMetadata) - self.state = .clientOpenServerClosed(.init(previousState: state)) - return state.headers - - case .clientClosedServerOpen(var state): - self.state = ._modifying - state.headers.formTrailers(status: status, metadata: customMetadata) - self.state = .clientClosedServerClosed(.init(previousState: state)) - return state.headers - - case .clientOpenServerIdle(var state): - self.state = ._modifying - state.headers.formTrailersOnly(status: status, metadata: customMetadata) - self.state = .clientOpenServerClosed(.init(previousState: state)) - return state.headers - - case .clientClosedServerIdle(var state): - self.state = ._modifying - state.headers.formTrailersOnly(status: status, metadata: customMetadata) - self.state = .clientClosedServerClosed(.init(previousState: state)) - return state.headers - - case .clientIdleServerIdle: - try self.invalidState( - "Server can't send status if client is idle." - ) - case .clientOpenServerClosed, .clientClosedServerClosed: - try self.invalidState( - "Server can't send anything if closed." - ) - case ._modifying: - preconditionFailure() - } - } - - private mutating func serverReceive( - headers: HPACKHeaders, - endStream: Bool, - configuration: GRPCStreamStateMachineConfiguration.ServerConfiguration - ) throws(InvalidState) -> OnMetadataReceived { - func closeServer( - from state: GRPCStreamStateMachineState.ClientIdleServerIdleState, - endStream: Bool - ) -> GRPCStreamStateMachineState { - if endStream { - return .clientClosedServerClosed(.init(previousState: state)) - } else { - return .clientOpenServerClosed(.init(previousState: state)) - } - } - - switch self.state { - case .clientIdleServerIdle(let state): - let contentType = headers.firstString(forKey: .contentType) - .flatMap { ContentType(value: $0) } - if contentType == nil { - self.state = .clientOpenServerClosed(.init(previousState: state)) - - // Respond with HTTP-level Unsupported Media Type status code. - var trailers = HPACKHeaders() - trailers.add("415", forKey: .status) - return .rejectRPC_serverOnly(trailers: trailers) - } - - guard let pathHeader = headers.firstString(forKey: .path) else { - self.state = closeServer(from: state, endStream: endStream) - return .rejectRPC_serverOnly( - trailers: .trailersOnly( - code: .invalidArgument, - message: "No \(GRPCHTTP2Keys.path.rawValue) header has been set." - ) - ) - } - - guard let path = MethodDescriptor(path: pathHeader) else { - self.state = closeServer(from: state, endStream: endStream) - return .rejectRPC_serverOnly( - trailers: .trailersOnly( - code: .unimplemented, - message: - "The given \(GRPCHTTP2Keys.path.rawValue) (\(pathHeader)) does not correspond to a valid method." - ) - ) - } - - let scheme = headers.firstString(forKey: .scheme).flatMap { Scheme(rawValue: $0) } - if scheme == nil { - self.state = closeServer(from: state, endStream: endStream) - return .rejectRPC_serverOnly( - trailers: .trailersOnly( - code: .invalidArgument, - message: ":scheme header must be present and one of \"http\" or \"https\"." - ) - ) - } - - guard let method = headers.firstString(forKey: .method), method == "POST" else { - self.state = closeServer(from: state, endStream: endStream) - return .rejectRPC_serverOnly( - trailers: .trailersOnly( - code: .invalidArgument, - message: ":method header is expected to be present and have a value of \"POST\"." - ) - ) - } - - // Firstly, find out if we support the client's chosen encoding, and reject - // the RPC if we don't. - let inboundEncoding: CompressionAlgorithm - let encodingValues = headers.values( - forHeader: GRPCHTTP2Keys.encoding.rawValue, - canonicalForm: true - ) - var encodingValuesIterator = encodingValues.makeIterator() - if let rawEncoding = encodingValuesIterator.next() { - guard encodingValuesIterator.next() == nil else { - self.state = closeServer(from: state, endStream: endStream) - return .rejectRPC_serverOnly( - trailers: .trailersOnly( - code: .internalError, - message: "\(GRPCHTTP2Keys.encoding) must contain no more than one value." - ) - ) - } - - guard let clientEncoding = CompressionAlgorithm(name: rawEncoding), - configuration.acceptedEncodings.contains(clientEncoding) - else { - self.state = closeServer(from: state, endStream: endStream) - var trailers = HPACKHeaders.trailersOnly( - code: .unimplemented, - message: """ - \(rawEncoding) compression is not supported; \ - supported algorithms are listed in grpc-accept-encoding - """ - ) - - for acceptedEncoding in configuration.acceptedEncodings.elements { - trailers.add(name: GRPCHTTP2Keys.acceptEncoding.rawValue, value: acceptedEncoding.name) - } - - return .rejectRPC_serverOnly(trailers: trailers) - } - - // Server supports client's encoding. - inboundEncoding = clientEncoding - } else { - inboundEncoding = .none - } - - // Secondly, find a compatible encoding the server can use to compress outbound messages, - // based on the encodings the client has advertised. - var outboundEncoding: CompressionAlgorithm = .none - let clientAdvertisedEncodings = headers.values( - forHeader: GRPCHTTP2Keys.acceptEncoding.rawValue, - canonicalForm: true - ) - // Find the preferred encoding and use it to compress responses. - for clientAdvertisedEncoding in clientAdvertisedEncodings { - if let algorithm = CompressionAlgorithm(name: clientAdvertisedEncoding), - configuration.acceptedEncodings.contains(algorithm) - { - outboundEncoding = algorithm - break - } - } - - if endStream { - self.state = .clientClosedServerIdle( - .init( - previousState: state, - compressionAlgorithm: outboundEncoding, - headers: headers - ) - ) - } else { - let compressor = Zlib.Method(encoding: outboundEncoding) - .flatMap { Zlib.Compressor(method: $0) } - let decompressor = Zlib.Method(encoding: inboundEncoding) - .flatMap { Zlib.Decompressor(method: $0) } - - self.state = .clientOpenServerIdle( - .init( - previousState: state, - compressor: compressor, - outboundCompression: outboundEncoding, - framer: GRPCMessageFramer(), - decompressor: decompressor, - deframer: GRPCMessageDeframer( - maxPayloadSize: state.maxPayloadSize, - decompressor: decompressor - ), - headers: headers - ) - ) - } - - return .receivedMetadata(Metadata(headers: headers), path) - - case .clientOpenServerIdle, .clientOpenServerOpen, .clientOpenServerClosed: - // Metadata has already been received, should only be sent once by clients. - return .protocolViolation_serverOnly - - case .clientClosedServerIdle, .clientClosedServerOpen, .clientClosedServerClosed: - try self.invalidState("Client can't have sent metadata if closed.") - - case ._modifying: - preconditionFailure() - } - } - - private mutating func serverReceive( - buffer: ByteBuffer, - endStream: Bool - ) throws(InvalidState) -> OnBufferReceivedAction { - let action: OnBufferReceivedAction - - switch self.state { - case .clientIdleServerIdle: - try self.invalidState("Can't have received a message if client is idle.") - - case .clientOpenServerIdle(var state): - self.state = ._modifying - // Deframer must be present on the server side, as we know the decompression - // algorithm from the moment the client opens. - do { - state.deframer!.append(buffer) - try state.deframer!.decode(into: &state.inboundMessageBuffer) - action = .readInbound - } catch { - let error = RPCError(code: .internalError, message: "Failed to decode message") - action = .forwardErrorAndClose_serverOnly(error) - } - - if endStream { - self.state = .clientClosedServerIdle(.init(previousState: state)) - } else { - self.state = .clientOpenServerIdle(state) - } - - case .clientOpenServerOpen(var state): - self.state = ._modifying - do { - state.deframer.append(buffer) - try state.deframer.decode(into: &state.inboundMessageBuffer) - action = .readInbound - } catch { - let error = RPCError(code: .internalError, message: "Failed to decode message") - action = .forwardErrorAndClose_serverOnly(error) - } - - if endStream { - self.state = .clientClosedServerOpen(.init(previousState: state)) - } else { - self.state = .clientOpenServerOpen(state) - } - - case .clientOpenServerClosed(let state): - // Client is not done sending request, but server has already closed. - // Ignore the rest of the request: do nothing, unless endStream is set, - // in which case close the client. - if endStream { - self.state = .clientClosedServerClosed(.init(previousState: state)) - } - - action = .doNothing - - case .clientClosedServerIdle, .clientClosedServerOpen, .clientClosedServerClosed: - try self.invalidState("Client can't send a message if closed.") - - case ._modifying: - preconditionFailure() - } - - return action - } - - private mutating func serverNextOutboundFrame() throws(InvalidState) -> OnNextOutboundFrame { - switch self.state { - case .clientIdleServerIdle, .clientOpenServerIdle, .clientClosedServerIdle: - try self.invalidState("Server is not open yet.") - - case .clientOpenServerOpen(var state): - self.state = ._modifying - let next = state.framer.nextResult(compressor: state.compressor) - self.state = .clientOpenServerOpen(state) - - if let next = next { - return OnNextOutboundFrame(result: next.result, promise: next.promise) - } else { - return .awaitMoreMessages - } - - case .clientClosedServerOpen(var state): - self.state = ._modifying - let next = state.framer.nextResult(compressor: state.compressor) - self.state = .clientClosedServerOpen(state) - - if let next = next { - return OnNextOutboundFrame(result: next.result, promise: next.promise) - } else { - return .awaitMoreMessages - } - - case .clientOpenServerClosed(var state): - self.state = ._modifying - let next = state.framer?.nextResult(compressor: state.compressor) - self.state = .clientOpenServerClosed(state) - - if let next = next { - return OnNextOutboundFrame(result: next.result, promise: next.promise) - } else { - return .noMoreMessages - } - - case .clientClosedServerClosed(var state): - self.state = ._modifying - let next = state.framer?.nextResult(compressor: state.compressor) - self.state = .clientClosedServerClosed(state) - - if let next = next { - return OnNextOutboundFrame(result: next.result, promise: next.promise) - } else { - return .noMoreMessages - } - - case ._modifying: - preconditionFailure() - } - } - - private mutating func serverNextInboundMessage() -> OnNextInboundMessage { - switch self.state { - case .clientOpenServerIdle(var state): - self.state = ._modifying - let request = state.inboundMessageBuffer.pop() - self.state = .clientOpenServerIdle(state) - return request.map { .receiveMessage($0) } ?? .awaitMoreMessages - - case .clientOpenServerOpen(var state): - self.state = ._modifying - let request = state.inboundMessageBuffer.pop() - self.state = .clientOpenServerOpen(state) - return request.map { .receiveMessage($0) } ?? .awaitMoreMessages - - case .clientClosedServerIdle(var state): - self.state = ._modifying - let request = state.inboundMessageBuffer.pop() - self.state = .clientClosedServerIdle(state) - return request.map { .receiveMessage($0) } ?? .noMoreMessages - - case .clientClosedServerOpen(var state): - self.state = ._modifying - let request = state.inboundMessageBuffer.pop() - self.state = .clientClosedServerOpen(state) - return request.map { .receiveMessage($0) } ?? .noMoreMessages - - case .clientOpenServerClosed, .clientClosedServerClosed: - // Server has closed, no need to read. - return .noMoreMessages - - case .clientIdleServerIdle: - return .awaitMoreMessages - - case ._modifying: - preconditionFailure() - } - } - - private mutating func serverUnexpectedInboundClose( - reason: UnexpectedInboundCloseReason - ) -> OnUnexpectedInboundClose { - switch self.state { - case .clientIdleServerIdle(let state): - self.state = .clientClosedServerClosed(.init(previousState: state)) - return OnUnexpectedInboundClose(serverCloseReason: reason) - - case .clientOpenServerIdle(let state): - self.state = .clientClosedServerClosed(.init(previousState: state)) - return OnUnexpectedInboundClose(serverCloseReason: reason) - - case .clientOpenServerOpen(let state): - self.state = .clientClosedServerClosed(.init(previousState: state)) - return OnUnexpectedInboundClose(serverCloseReason: reason) - - case .clientOpenServerClosed(let state): - self.state = .clientClosedServerClosed(.init(previousState: state)) - return OnUnexpectedInboundClose(serverCloseReason: reason) - - case .clientClosedServerIdle, .clientClosedServerOpen, .clientClosedServerClosed: - return .doNothing - - case ._modifying: - preconditionFailure() - } - } -} - -extension MethodDescriptor { - init?(path: String) { - var view = path[...] - guard view.popFirst() == "/" else { return nil } - - // Find the index of the "/" separating the service and method names. - guard var index = view.firstIndex(of: "/") else { return nil } - - let service = String(view[.. String? { - self.values(forHeader: key.rawValue, canonicalForm: canonicalForm).first(where: { _ in true }) - .map { - String($0) - } - } - - fileprivate mutating func add(_ value: String, forKey key: GRPCHTTP2Keys) { - self.add(name: key.rawValue, value: value) - } - - fileprivate static func trailersOnly(code: Status.Code, message: String) -> Self { - var trailers = HPACKHeaders() - HPACKHeaders.formTrailers( - &trailers, - isTrailersOnly: true, - status: Status(code: code, message: message), - metadata: [:] - ) - return trailers - } - - fileprivate mutating func formTrailersOnly(status: Status, metadata: Metadata = [:]) { - Self.formTrailers(&self, isTrailersOnly: true, status: status, metadata: metadata) - } - - fileprivate mutating func formTrailers(status: Status, metadata: Metadata = [:]) { - Self.formTrailers(&self, isTrailersOnly: false, status: status, metadata: metadata) - } - - private static func formTrailers( - _ trailers: inout HPACKHeaders, - isTrailersOnly: Bool, - status: Status, - metadata: Metadata - ) { - trailers.removeAll(keepingCapacity: true) - - if isTrailersOnly { - trailers.reserveCapacity(4 + metadata.count) - trailers.add("200", forKey: .status) - trailers.add(ContentType.grpc.canonicalValue, forKey: .contentType) - } else { - trailers.reserveCapacity(2 + metadata.count) - } - - trailers.add(String(status.code.rawValue), forKey: .grpcStatus) - if !status.message.isEmpty, let encoded = GRPCStatusMessageMarshaller.marshall(status.message) { - trailers.add(encoded, forKey: .grpcStatusMessage) - } - - for (key, value) in metadata { - trailers.add(name: key, value: value.encoded()) - } - } -} - -extension Zlib.Method { - init?(encoding: CompressionAlgorithm) { - switch encoding { - case .none: - return nil - case .deflate: - self = .deflate - case .gzip: - self = .gzip - default: - return nil - } - } -} - -extension Metadata { - init(headers: HPACKHeaders) { - var metadata = Metadata() - metadata.reserveCapacity(headers.count) - for header in headers { - if header.name.hasSuffix("-bin") { - do { - let decodedBinary = try header.value.base64Decoded() - metadata.addBinary(decodedBinary, forKey: header.name) - } catch { - metadata.addString(header.value, forKey: header.name) - } - } else { - metadata.addString(header.value, forKey: header.name) - } - } - self = metadata - } -} - -extension Status.Code { - // See https://github.com/grpc/grpc/blob/master/doc/http-grpc-status-mapping.md - init(httpStatusCode: HTTPResponseStatus) { - switch httpStatusCode { - case .badRequest: - self = .internalError - case .unauthorized: - self = .unauthenticated - case .forbidden: - self = .permissionDenied - case .notFound: - self = .unimplemented - case .tooManyRequests, .badGateway, .serviceUnavailable, .gatewayTimeout: - self = .unavailable - default: - self = .unknown - } - } -} - -extension MethodDescriptor { - var path: String { - return "/\(self.service)/\(self.method)" - } -} - -extension RPCError { - @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) - fileprivate init(_ reason: GRPCStreamStateMachine.UnexpectedInboundCloseReason) { - switch reason { - case .streamReset: - self = RPCError( - code: .unavailable, - message: "Stream unexpectedly closed: a RST_STREAM frame was received." - ) - case .channelInactive: - self = RPCError(code: .unavailable, message: "Stream unexpectedly closed.") - case .errorThrown: - self = RPCError(code: .unavailable, message: "Stream unexpectedly closed with error.") - } - } -} - -extension Status { - @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) - fileprivate init(_ error: RPCError) { - self = Status(code: Status.Code(error.code), message: error.message) - } -} - -extension RPCError { - @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) - init(_ invalidState: GRPCStreamStateMachine.InvalidState) { - self = RPCError(code: .internalError, message: "Invalid state", cause: invalidState) - } -} diff --git a/Sources/GRPCHTTP2Core/Internal/ConstantAsyncSequence.swift b/Sources/GRPCHTTP2Core/Internal/ConstantAsyncSequence.swift deleted file mode 100644 index d1ecef17e..000000000 --- a/Sources/GRPCHTTP2Core/Internal/ConstantAsyncSequence.swift +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -internal import GRPCCore - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -private struct ConstantAsyncSequence: AsyncSequence, Sendable { - private let element: Element - - init(element: Element) { - self.element = element - } - - func makeAsyncIterator() -> AsyncIterator { - return AsyncIterator(element: self.element) - } - - struct AsyncIterator: AsyncIteratorProtocol { - private let element: Element - - fileprivate init(element: Element) { - self.element = element - } - - func next() async throws -> Element? { - return self.element - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension RPCAsyncSequence where Element: Sendable, Failure == any Error { - static func constant(_ element: Element) -> RPCAsyncSequence { - return RPCAsyncSequence(wrapping: ConstantAsyncSequence(element: element)) - } -} diff --git a/Sources/GRPCHTTP2Core/Internal/ContentType.swift b/Sources/GRPCHTTP2Core/Internal/ContentType.swift deleted file mode 100644 index 2e098d39f..000000000 --- a/Sources/GRPCHTTP2Core/Internal/ContentType.swift +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -// See: -// - https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md -enum ContentType { - case grpc - - init?(value: String) { - switch value { - case "application/grpc", - "application/grpc+proto": - self = .grpc - - default: - return nil - } - } - - var canonicalValue: String { - switch self { - case .grpc: - // This is more widely supported than "application/grpc+proto" - return "application/grpc" - } - } -} diff --git a/Sources/GRPCHTTP2Core/Internal/DiscardingTaskGroup+CancellableHandle.swift b/Sources/GRPCHTTP2Core/Internal/DiscardingTaskGroup+CancellableHandle.swift deleted file mode 100644 index 11f818c28..000000000 --- a/Sources/GRPCHTTP2Core/Internal/DiscardingTaskGroup+CancellableHandle.swift +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) -extension DiscardingTaskGroup { - /// Adds a child task to the group which is individually cancellable. - /// - /// - Parameter operation: The task to add to the group. - /// - Returns: A handle which can be used to cancel the task without cancelling the rest of - /// the group. - @inlinable - mutating func addCancellableTask( - _ operation: @Sendable @escaping () async -> Void - ) -> CancellableTaskHandle { - let signal = AsyncStream.makeStream(of: Void.self) - self.addTask { - return await withTaskGroup(of: FinishedOrCancelled.self) { group in - group.addTask { - await operation() - return .finished - } - - group.addTask { - for await _ in signal.stream {} - return .cancelled - } - - let first = await group.next()! - group.cancelAll() - let second = await group.next()! - - switch (first, second) { - case (.finished, .cancelled), (.cancelled, .finished): - return - default: - fatalError("Internal inconsistency") - } - } - } - - return CancellableTaskHandle(continuation: signal.continuation) - } - - @usableFromInline - enum FinishedOrCancelled: Sendable { - case finished - case cancelled - } -} - -@usableFromInline -@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) -struct CancellableTaskHandle: Sendable { - @usableFromInline - private(set) var continuation: AsyncStream.Continuation - - @inlinable - init(continuation: AsyncStream.Continuation) { - self.continuation = continuation - } - - @inlinable - func cancel() { - self.continuation.finish() - } -} diff --git a/Sources/GRPCHTTP2Core/Internal/GRPCStatusMessageMarshaller.swift b/Sources/GRPCHTTP2Core/Internal/GRPCStatusMessageMarshaller.swift deleted file mode 100644 index 4f8b1eb40..000000000 --- a/Sources/GRPCHTTP2Core/Internal/GRPCStatusMessageMarshaller.swift +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -enum GRPCStatusMessageMarshaller { - /// Adds percent encoding to the given message. - /// - /// - Parameter message: Message to percent encode. - /// - Returns: Percent encoded string, or `nil` if it could not be encoded. - static func marshall(_ message: String) -> String? { - return percentEncode(message) - } - - /// Removes percent encoding from the given message. - /// - /// - Parameter message: Message to remove encoding from. - /// - Returns: The string with percent encoding removed, or the input string if the encoding - /// could not be removed. - static func unmarshall(_ message: String) -> String { - return removePercentEncoding(message) - } -} - -extension GRPCStatusMessageMarshaller { - /// Adds percent encoding to the given message. - /// - /// gRPC uses percent encoding as defined in RFC 3986 § 2.1 but with a different set of restricted - /// characters. The allowed characters are all visible printing characters except for (`%`, - /// `0x25`). That is: `0x20`-`0x24`, `0x26`-`0x7E`. - /// - /// - Parameter message: The message to encode. - /// - Returns: Percent encoded string, or `nil` if it could not be encoded. - private static func percentEncode(_ message: String) -> String? { - let utf8 = message.utf8 - - let encodedLength = self.percentEncodedLength(for: utf8) - // Fast-path: all characters are valid, nothing to encode. - if encodedLength == utf8.count { - return message - } - - var bytes: [UInt8] = [] - bytes.reserveCapacity(encodedLength) - - for char in message.utf8 { - switch char { - // See: https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#responses - case 0x20 ... 0x24, - 0x26 ... 0x7E: - bytes.append(char) - - default: - bytes.append(UInt8(ascii: "%")) - bytes.append(self.toHex(char >> 4)) - bytes.append(self.toHex(char & 0xF)) - } - } - - return String(decoding: bytes, as: UTF8.self) - } - - /// Returns the percent encoded length of the given `UTF8View`. - private static func percentEncodedLength(for view: String.UTF8View) -> Int { - var count = view.count - for byte in view { - switch byte { - case 0x20 ... 0x24, - 0x26 ... 0x7E: - () - - default: - count += 2 - } - } - return count - } - - /// Encode the given byte as hexadecimal. - /// - /// - Precondition: Only the four least significant bits may be set. - /// - Parameter nibble: The nibble to convert to hexadecimal. - private static func toHex(_ nibble: UInt8) -> UInt8 { - assert(nibble & 0xF == nibble) - - switch nibble { - case 0 ... 9: - return nibble &+ UInt8(ascii: "0") - default: - return nibble &+ (UInt8(ascii: "A") &- 10) - } - } - - /// Remove gRPC percent encoding from `message`. If any portion of the string could not be decoded - /// then the encoded message will be returned. - /// - /// - Parameter message: The message to remove percent encoding from. - /// - Returns: The decoded message. - private static func removePercentEncoding(_ message: String) -> String { - let utf8 = message.utf8 - - let decodedLength = self.percentDecodedLength(for: utf8) - // Fast-path: no decoding to do! Note that we may also have detected that the encoding is - // invalid, in which case we will return the encoded message: this is fine. - if decodedLength == utf8.count { - return message - } - - var chars: [UInt8] = [] - // We can't decode more characters than are already encoded. - chars.reserveCapacity(decodedLength) - - var currentIndex = utf8.startIndex - let endIndex = utf8.endIndex - - while currentIndex < endIndex { - let byte = utf8[currentIndex] - - switch byte { - case UInt8(ascii: "%"): - guard let (nextIndex, nextNextIndex) = utf8.nextTwoIndices(after: currentIndex), - let nextHex = fromHex(utf8[nextIndex]), - let nextNextHex = fromHex(utf8[nextNextIndex]) - else { - // If we can't decode the message, aborting and returning the encoded message is fine - // according to the spec. - return message - } - chars.append((nextHex << 4) | nextNextHex) - currentIndex = nextNextIndex - - default: - chars.append(byte) - } - - currentIndex = utf8.index(after: currentIndex) - } - - return String(decoding: chars, as: Unicode.UTF8.self) - } - - /// Returns the expected length of the decoded `UTF8View`. - private static func percentDecodedLength(for view: String.UTF8View) -> Int { - var encoded = 0 - - for byte in view { - switch byte { - case UInt8(ascii: "%"): - // This can't overflow since it can't be larger than view.count. - encoded &+= 1 - - default: - () - } - } - - let notEncoded = view.count - (encoded * 3) - - guard notEncoded >= 0 else { - // We've received gibberish: more '%' than expected. gRPC allows for the status message to - // be left encoded should it be incorrectly encoded. We'll do exactly that by returning - // the number of bytes in the view which will causes us to take the fast-path exit. - return view.count - } - - return notEncoded + encoded - } - - private static func fromHex(_ byte: UInt8) -> UInt8? { - switch byte { - case UInt8(ascii: "0") ... UInt8(ascii: "9"): - return byte &- UInt8(ascii: "0") - case UInt8(ascii: "A") ... UInt8(ascii: "Z"): - return byte &- (UInt8(ascii: "A") &- 10) - case UInt8(ascii: "a") ... UInt8(ascii: "z"): - return byte &- (UInt8(ascii: "a") &- 10) - default: - return nil - } - } -} - -extension String.UTF8View { - /// Return the next two valid indices after the given index. The indices are considered valid if - /// they less than `endIndex`. - fileprivate func nextTwoIndices(after index: Index) -> (Index, Index)? { - let secondIndex = self.index(index, offsetBy: 2) - guard secondIndex < self.endIndex else { - return nil - } - - return (self.index(after: index), secondIndex) - } -} diff --git a/Sources/GRPCHTTP2Core/Internal/NIOChannelPipeline+GRPC.swift b/Sources/GRPCHTTP2Core/Internal/NIOChannelPipeline+GRPC.swift deleted file mode 100644 index cade0f581..000000000 --- a/Sources/GRPCHTTP2Core/Internal/NIOChannelPipeline+GRPC.swift +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -package import GRPCCore -package import NIOCore -internal import NIOHPACK -package import NIOHTTP2 - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -extension ChannelPipeline.SynchronousOperations { - package typealias HTTP2ConnectionChannel = NIOAsyncChannel - package typealias HTTP2StreamMultiplexer = NIOHTTP2Handler.AsyncStreamMultiplexer< - (NIOAsyncChannel, EventLoopFuture) - > - - package func configureGRPCServerPipeline( - channel: any Channel, - compressionConfig: HTTP2ServerTransport.Config.Compression, - connectionConfig: HTTP2ServerTransport.Config.Connection, - http2Config: HTTP2ServerTransport.Config.HTTP2, - rpcConfig: HTTP2ServerTransport.Config.RPC, - requireALPN: Bool, - scheme: Scheme - ) throws -> (HTTP2ConnectionChannel, HTTP2StreamMultiplexer) { - let serverConnectionHandler = ServerConnectionManagementHandler( - eventLoop: self.eventLoop, - maxIdleTime: connectionConfig.maxIdleTime.map { TimeAmount($0) }, - maxAge: connectionConfig.maxAge.map { TimeAmount($0) }, - maxGraceTime: connectionConfig.maxGraceTime.map { TimeAmount($0) }, - keepaliveTime: TimeAmount(connectionConfig.keepalive.time), - keepaliveTimeout: TimeAmount(connectionConfig.keepalive.timeout), - allowKeepaliveWithoutCalls: connectionConfig.keepalive.clientBehavior.allowWithoutCalls, - minPingIntervalWithoutCalls: TimeAmount( - connectionConfig.keepalive.clientBehavior.minPingIntervalWithoutCalls - ), - requireALPN: requireALPN - ) - let flushNotificationHandler = GRPCServerFlushNotificationHandler( - serverConnectionManagementHandler: serverConnectionHandler - ) - try self.addHandler(flushNotificationHandler) - - let clampedTargetWindowSize = self.clampTargetWindowSize(http2Config.targetWindowSize) - let clampedMaxFrameSize = self.clampMaxFrameSize(http2Config.maxFrameSize) - - var http2HandlerConnectionConfiguration = NIOHTTP2Handler.ConnectionConfiguration() - var http2HandlerHTTP2Settings = HTTP2Settings([ - HTTP2Setting(parameter: .initialWindowSize, value: clampedTargetWindowSize), - HTTP2Setting(parameter: .maxFrameSize, value: clampedMaxFrameSize), - HTTP2Setting(parameter: .maxHeaderListSize, value: HPACKDecoder.defaultMaxHeaderListSize), - ]) - if let maxConcurrentStreams = http2Config.maxConcurrentStreams { - http2HandlerHTTP2Settings.append( - HTTP2Setting(parameter: .maxConcurrentStreams, value: maxConcurrentStreams) - ) - } - http2HandlerConnectionConfiguration.initialSettings = http2HandlerHTTP2Settings - - var http2HandlerStreamConfiguration = NIOHTTP2Handler.StreamConfiguration() - http2HandlerStreamConfiguration.targetWindowSize = clampedTargetWindowSize - - let streamMultiplexer = try self.configureAsyncHTTP2Pipeline( - mode: .server, - streamDelegate: serverConnectionHandler.http2StreamDelegate, - configuration: NIOHTTP2Handler.Configuration( - connection: http2HandlerConnectionConfiguration, - stream: http2HandlerStreamConfiguration - ) - ) { streamChannel in - return streamChannel.eventLoop.makeCompletedFuture { - let methodDescriptorPromise = streamChannel.eventLoop.makePromise(of: MethodDescriptor.self) - let streamHandler = GRPCServerStreamHandler( - scheme: scheme, - acceptedEncodings: compressionConfig.enabledAlgorithms, - maxPayloadSize: rpcConfig.maxRequestPayloadSize, - methodDescriptorPromise: methodDescriptorPromise - ) - try streamChannel.pipeline.syncOperations.addHandler(streamHandler) - - let asyncStreamChannel = try NIOAsyncChannel( - wrappingChannelSynchronously: streamChannel - ) - return (asyncStreamChannel, methodDescriptorPromise.futureResult) - } - } - - try self.addHandler(serverConnectionHandler) - - let connectionChannel = try NIOAsyncChannel( - wrappingChannelSynchronously: channel - ) - - return (connectionChannel, streamMultiplexer) - } -} - -extension ChannelPipeline.SynchronousOperations { - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - package func configureGRPCClientPipeline( - channel: any Channel, - config: GRPCChannel.Config - ) throws -> ( - NIOAsyncChannel, - NIOHTTP2Handler.AsyncStreamMultiplexer - ) { - let clampedTargetWindowSize = self.clampTargetWindowSize(config.http2.targetWindowSize) - let clampedMaxFrameSize = self.clampMaxFrameSize(config.http2.maxFrameSize) - - // Use NIOs defaults as a starting point. - var http2 = NIOHTTP2Handler.Configuration() - http2.stream.targetWindowSize = clampedTargetWindowSize - http2.connection.initialSettings = [ - // Disallow servers from creating push streams. - HTTP2Setting(parameter: .enablePush, value: 0), - // Set the initial window size and max frame size to the clamped configured values. - HTTP2Setting(parameter: .initialWindowSize, value: clampedTargetWindowSize), - HTTP2Setting(parameter: .maxFrameSize, value: clampedMaxFrameSize), - // Use NIOs default max header list size (16kB) - HTTP2Setting(parameter: .maxHeaderListSize, value: HPACKDecoder.defaultMaxHeaderListSize), - ] - - let connectionHandler = ClientConnectionHandler( - eventLoop: self.eventLoop, - maxIdleTime: config.connection.maxIdleTime.map { TimeAmount($0) }, - keepaliveTime: config.connection.keepalive.map { TimeAmount($0.time) }, - keepaliveTimeout: config.connection.keepalive.map { TimeAmount($0.timeout) }, - keepaliveWithoutCalls: config.connection.keepalive?.allowWithoutCalls ?? false - ) - - let multiplexer = try self.configureAsyncHTTP2Pipeline( - mode: .client, - streamDelegate: connectionHandler.http2StreamDelegate, - configuration: http2 - ) { stream in - // Shouldn't happen, push-promises are disabled so the server shouldn't be able to - // open streams. - stream.close() - } - - try self.addHandler(connectionHandler) - - let connection = try NIOAsyncChannel( - wrappingChannelSynchronously: channel, - configuration: NIOAsyncChannel.Configuration( - inboundType: ClientConnectionEvent.self, - outboundType: Void.self - ) - ) - - return (connection, multiplexer) - } -} - -extension ChannelPipeline.SynchronousOperations { - /// Max frame size must be in the range `2^14 ..< 2^24` (RFC 9113 § 4.2). - fileprivate func clampMaxFrameSize(_ maxFrameSize: Int) -> Int { - let clampedMaxFrameSize: Int - if maxFrameSize >= (1 << 24) { - clampedMaxFrameSize = (1 << 24) - 1 - } else if maxFrameSize < (1 << 14) { - clampedMaxFrameSize = (1 << 14) - } else { - clampedMaxFrameSize = maxFrameSize - } - return clampedMaxFrameSize - } - - /// Window size which mustn't exceed `2^31 - 1` (RFC 9113 § 6.5.2). - internal func clampTargetWindowSize(_ targetWindowSize: Int) -> Int { - min(targetWindowSize, (1 << 31) - 1) - } -} diff --git a/Sources/GRPCHTTP2Core/Internal/NIOSocketAddress+GRPCSocketAddress.swift b/Sources/GRPCHTTP2Core/Internal/NIOSocketAddress+GRPCSocketAddress.swift deleted file mode 100644 index e27b07659..000000000 --- a/Sources/GRPCHTTP2Core/Internal/NIOSocketAddress+GRPCSocketAddress.swift +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -private import GRPCCore -package import NIOCore - -extension GRPCHTTP2Core.SocketAddress { - package init(_ nioSocketAddress: NIOCore.SocketAddress) { - switch nioSocketAddress { - case .v4(let address): - self = .ipv4( - host: address.host, - port: nioSocketAddress.port ?? 0 - ) - - case .v6(let address): - self = .ipv6( - host: address.host, - port: nioSocketAddress.port ?? 0 - ) - - case .unixDomainSocket: - self = .unixDomainSocket(path: nioSocketAddress.pathname ?? "") - } - } -} - -extension NIOCore.SocketAddress { - package init(_ socketAddress: GRPCHTTP2Core.SocketAddress) throws { - if let ipv4 = socketAddress.ipv4 { - self = try Self(ipv4) - } else if let ipv6 = socketAddress.ipv6 { - self = try Self(ipv6) - } else if let unixDomainSocket = socketAddress.unixDomainSocket { - self = try Self(unixDomainSocket) - } else { - throw RPCError( - code: .internalError, - message: - "Unsupported mapping to NIOCore/SocketAddress for GRPCHTTP2Core/SocketAddress: \(socketAddress)." - ) - } - } - - package init(_ address: GRPCHTTP2Core.SocketAddress.IPv4) throws { - try self.init(ipAddress: address.host, port: address.port) - } - - package init(_ address: GRPCHTTP2Core.SocketAddress.IPv6) throws { - try self.init(ipAddress: address.host, port: address.port) - } - - package init(_ address: GRPCHTTP2Core.SocketAddress.UnixDomainSocket) throws { - try self.init(unixDomainSocketPath: address.path) - } -} diff --git a/Sources/GRPCHTTP2Core/Internal/ProcessUniqueID.swift b/Sources/GRPCHTTP2Core/Internal/ProcessUniqueID.swift deleted file mode 100644 index 5f6f32f7e..000000000 --- a/Sources/GRPCHTTP2Core/Internal/ProcessUniqueID.swift +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -private import Synchronization - -/// An ID which is unique within this process. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -struct ProcessUniqueID: Hashable, Sendable, CustomStringConvertible { - private static let source = Atomic(UInt64(0)) - private let rawValue: UInt64 - - init() { - let (_, newValue) = Self.source.add(1, ordering: .relaxed) - self.rawValue = newValue - } - - var description: String { - String(describing: self.rawValue) - } -} - -/// A process-unique ID for a subchannel. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -package struct SubchannelID: Hashable, Sendable, CustomStringConvertible { - private let id = ProcessUniqueID() - package init() {} - package var description: String { - "subchan_\(self.id)" - } -} - -/// A process-unique ID for a load-balancer. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -struct LoadBalancerID: Hashable, Sendable, CustomStringConvertible { - private let id = ProcessUniqueID() - var description: String { - "lb_\(self.id)" - } -} - -/// A process-unique ID for an entry in a queue. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -struct QueueEntryID: Hashable, Sendable, CustomStringConvertible { - private let id = ProcessUniqueID() - var description: String { - "q_entry_\(self.id)" - } -} diff --git a/Sources/GRPCHTTP2Core/Internal/Result+Catching.swift b/Sources/GRPCHTTP2Core/Internal/Result+Catching.swift deleted file mode 100644 index 1cd809e42..000000000 --- a/Sources/GRPCHTTP2Core/Internal/Result+Catching.swift +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -extension Result where Failure == any Error { - /// Like `Result(catching:)`, but `async`. - /// - /// - Parameter body: An `async` closure to catch the result of. - @inlinable - init(catching body: () async throws -> Success) async { - do { - self = .success(try await body()) - } catch { - self = .failure(error) - } - } -} diff --git a/Sources/GRPCHTTP2Core/Internal/Timer.swift b/Sources/GRPCHTTP2Core/Internal/Timer.swift deleted file mode 100644 index bfc4ff29a..000000000 --- a/Sources/GRPCHTTP2Core/Internal/Timer.swift +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -package import NIOCore - -package struct Timer { - /// The delay to wait before running the task. - private let delay: TimeAmount - /// The task to run, if scheduled. - private var task: Kind? - /// Whether the task to schedule is repeated. - private let `repeat`: Bool - - private enum Kind { - case once(Scheduled) - case repeated(RepeatedTask) - - func cancel() { - switch self { - case .once(let task): - task.cancel() - case .repeated(let task): - task.cancel() - } - } - } - - package init(delay: TimeAmount, repeat: Bool = false) { - self.delay = delay - self.task = nil - self.repeat = `repeat` - } - - /// Schedule a task on the given `EventLoop`. - package mutating func schedule( - on eventLoop: any EventLoop, - work: @escaping @Sendable () throws -> Void - ) { - self.task?.cancel() - - if self.repeat { - let task = eventLoop.scheduleRepeatedTask(initialDelay: self.delay, delay: self.delay) { _ in - try work() - } - self.task = .repeated(task) - } else { - let task = eventLoop.scheduleTask(in: self.delay, work) - self.task = .once(task) - } - } - - /// Cancels the task, if one was scheduled. - package mutating func cancel() { - self.task?.cancel() - self.task = nil - } -} diff --git a/Sources/GRPCHTTP2Core/ListeningServerTransport.swift b/Sources/GRPCHTTP2Core/ListeningServerTransport.swift deleted file mode 100644 index 20150d360..000000000 --- a/Sources/GRPCHTTP2Core/ListeningServerTransport.swift +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -public import GRPCCore - -/// A transport which refines `ServerTransport` to provide the socket address of a listening -/// server. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -public protocol ListeningServerTransport: ServerTransport { - /// Returns the listening address of the server transport once it has started. - var listeningAddress: SocketAddress { get async throws } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension GRPCServer { - /// Returns the listening address of the server transport once it has started. - /// - /// This will be `nil` if the transport doesn't conform to ``ListeningServerTransport``. - public var listeningAddress: SocketAddress? { - get async throws { - if let listener = self.transport as? (any ListeningServerTransport) { - return try await listener.listeningAddress - } else { - return nil - } - } - } -} diff --git a/Sources/GRPCHTTP2Core/OneOrManyQueue.swift b/Sources/GRPCHTTP2Core/OneOrManyQueue.swift deleted file mode 100644 index fdf88186c..000000000 --- a/Sources/GRPCHTTP2Core/OneOrManyQueue.swift +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -internal import DequeModule - -/// A FIFO-queue which allows for a single element to be stored on the stack and defers to a -/// heap-implementation if further elements are added. -/// -/// This is useful when optimising for unary streams where avoiding the cost of a heap -/// allocation is desirable. -internal struct OneOrManyQueue: Collection { - private var backing: Backing - - private enum Backing: Collection { - case none - case one(Element) - case many(Deque) - - var startIndex: Int { - switch self { - case .none, .one: - return 0 - case let .many(elements): - return elements.startIndex - } - } - - var endIndex: Int { - switch self { - case .none: - return 0 - case .one: - return 1 - case let .many(elements): - return elements.endIndex - } - } - - subscript(index: Int) -> Element { - switch self { - case .none: - fatalError("Invalid index") - case let .one(element): - assert(index == 0) - return element - case let .many(elements): - return elements[index] - } - } - - func index(after index: Int) -> Int { - switch self { - case .none: - return 0 - case .one: - return 1 - case let .many(elements): - return elements.index(after: index) - } - } - - var count: Int { - switch self { - case .none: - return 0 - case .one: - return 1 - case let .many(elements): - return elements.count - } - } - - var isEmpty: Bool { - switch self { - case .none: - return true - case .one: - return false - case let .many(elements): - return elements.isEmpty - } - } - - mutating func append(_ element: Element) { - switch self { - case .none: - self = .one(element) - case let .one(one): - var elements = Deque() - elements.reserveCapacity(16) - elements.append(one) - elements.append(element) - self = .many(elements) - case var .many(elements): - self = .none - elements.append(element) - self = .many(elements) - } - } - - mutating func pop() -> Element? { - switch self { - case .none: - return nil - case let .one(element): - self = .none - return element - case var .many(many): - self = .none - let element = many.popFirst() - self = .many(many) - return element - } - } - } - - init() { - self.backing = .none - } - - var isEmpty: Bool { - return self.backing.isEmpty - } - - var count: Int { - return self.backing.count - } - - var startIndex: Int { - return self.backing.startIndex - } - - var endIndex: Int { - return self.backing.endIndex - } - - subscript(index: Int) -> Element { - return self.backing[index] - } - - func index(after index: Int) -> Int { - return self.backing.index(after: index) - } - - mutating func append(_ element: Element) { - self.backing.append(element) - } - - mutating func pop() -> Element? { - return self.backing.pop() - } -} diff --git a/Sources/GRPCHTTP2Core/Server/CommonHTTP2ServerTransport.swift b/Sources/GRPCHTTP2Core/Server/CommonHTTP2ServerTransport.swift deleted file mode 100644 index 769db9bf7..000000000 --- a/Sources/GRPCHTTP2Core/Server/CommonHTTP2ServerTransport.swift +++ /dev/null @@ -1,264 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -package import GRPCCore -package import NIOCore -package import NIOExtras -private import NIOHTTP2 -private import Synchronization - -/// Provides the common functionality for a `NIO`-based server transport. -/// -/// - SeeAlso: ``HTTP2ListenerFactory``. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -package final class CommonHTTP2ServerTransport< - ListenerFactory: HTTP2ListenerFactory ->: ServerTransport, ListeningServerTransport { - private let eventLoopGroup: any EventLoopGroup - private let address: SocketAddress - private let listeningAddressState: Mutex - private let serverQuiescingHelper: ServerQuiescingHelper - private let factory: ListenerFactory - - private enum State { - case idle(EventLoopPromise) - case listening(EventLoopFuture) - case closedOrInvalidAddress(RuntimeError) - - var listeningAddressFuture: EventLoopFuture { - get throws { - switch self { - case .idle(let eventLoopPromise): - return eventLoopPromise.futureResult - case .listening(let eventLoopFuture): - return eventLoopFuture - case .closedOrInvalidAddress(let runtimeError): - throw runtimeError - } - } - } - - enum OnBound { - case succeedPromise(_ promise: EventLoopPromise, address: SocketAddress) - case failPromise(_ promise: EventLoopPromise, error: RuntimeError) - } - - mutating func addressBound( - _ address: NIOCore.SocketAddress?, - userProvidedAddress: SocketAddress - ) -> OnBound { - switch self { - case .idle(let listeningAddressPromise): - if let address { - self = .listening(listeningAddressPromise.futureResult) - return .succeedPromise(listeningAddressPromise, address: SocketAddress(address)) - } else if userProvidedAddress.virtualSocket != nil { - self = .listening(listeningAddressPromise.futureResult) - return .succeedPromise(listeningAddressPromise, address: userProvidedAddress) - } else { - assertionFailure("Unknown address type") - let invalidAddressError = RuntimeError( - code: .transportError, - message: "Unknown address type returned by transport." - ) - self = .closedOrInvalidAddress(invalidAddressError) - return .failPromise(listeningAddressPromise, error: invalidAddressError) - } - - case .listening, .closedOrInvalidAddress: - fatalError("Invalid state: addressBound should only be called once and when in idle state") - } - } - - enum OnClose { - case failPromise(EventLoopPromise, error: RuntimeError) - case doNothing - } - - mutating func close() -> OnClose { - let serverStoppedError = RuntimeError( - code: .serverIsStopped, - message: """ - There is no listening address bound for this server: there may have been \ - an error which caused the transport to close, or it may have shut down. - """ - ) - - switch self { - case .idle(let listeningAddressPromise): - self = .closedOrInvalidAddress(serverStoppedError) - return .failPromise(listeningAddressPromise, error: serverStoppedError) - - case .listening: - self = .closedOrInvalidAddress(serverStoppedError) - return .doNothing - - case .closedOrInvalidAddress: - return .doNothing - } - } - } - - /// The listening address for this server transport. - /// - /// It is an `async` property because it will only return once the address has been successfully bound. - /// - /// - Throws: A runtime error will be thrown if the address could not be bound or is not bound any - /// longer, because the transport isn't listening anymore. It can also throw if the transport returned an - /// invalid address. - package var listeningAddress: SocketAddress { - get async throws { - try await self.listeningAddressState - .withLock { try $0.listeningAddressFuture } - .get() - } - } - - package init( - address: SocketAddress, - eventLoopGroup: any EventLoopGroup, - quiescingHelper: ServerQuiescingHelper, - listenerFactory: ListenerFactory - ) { - self.eventLoopGroup = eventLoopGroup - self.address = address - - let eventLoop = eventLoopGroup.any() - self.listeningAddressState = Mutex(.idle(eventLoop.makePromise())) - - self.factory = listenerFactory - self.serverQuiescingHelper = quiescingHelper - } - - package func listen( - streamHandler: @escaping @Sendable ( - _ stream: RPCStream, - _ context: ServerContext - ) async -> Void - ) async throws { - defer { - switch self.listeningAddressState.withLock({ $0.close() }) { - case .failPromise(let promise, let error): - promise.fail(error) - case .doNothing: - () - } - } - - let serverChannel = try await self.factory.makeListeningChannel( - eventLoopGroup: self.eventLoopGroup, - address: self.address, - serverQuiescingHelper: self.serverQuiescingHelper - ) - - let action = self.listeningAddressState.withLock { - $0.addressBound( - serverChannel.channel.localAddress, - userProvidedAddress: self.address - ) - } - switch action { - case .succeedPromise(let promise, let address): - promise.succeed(address) - case .failPromise(let promise, let error): - promise.fail(error) - } - - try await serverChannel.executeThenClose { inbound in - try await withThrowingDiscardingTaskGroup { group in - for try await (connectionChannel, streamMultiplexer) in inbound { - group.addTask { - try await self.handleConnection( - connectionChannel, - multiplexer: streamMultiplexer, - streamHandler: streamHandler - ) - } - } - } - } - } - - private func handleConnection( - _ connection: NIOAsyncChannel, - multiplexer: ChannelPipeline.SynchronousOperations.HTTP2StreamMultiplexer, - streamHandler: @escaping @Sendable ( - _ stream: RPCStream, - _ context: ServerContext - ) async -> Void - ) async throws { - try await connection.executeThenClose { inbound, _ in - await withDiscardingTaskGroup { group in - group.addTask { - do { - for try await _ in inbound {} - } catch { - // We don't want to close the channel if one connection throws. - return - } - } - - do { - for try await (stream, descriptor) in multiplexer.inbound { - group.addTask { - await self.handleStream(stream, handler: streamHandler, descriptor: descriptor) - } - } - } catch { - return - } - } - } - } - - private func handleStream( - _ stream: NIOAsyncChannel, - handler streamHandler: @escaping @Sendable ( - _ stream: RPCStream, - _ context: ServerContext - ) async -> Void, - descriptor: EventLoopFuture - ) async { - // It's okay to ignore these errors: - // - If we get an error because the http2Stream failed to close, then there's nothing we can do - // - If we get an error because the inner closure threw, then the only possible scenario in which - // that could happen is if methodDescriptor.get() throws - in which case, it means we never got - // the RPC metadata, which means we can't do anything either and it's okay to just kill the stream. - try? await stream.executeThenClose { inbound, outbound in - guard let descriptor = try? await descriptor.get() else { - return - } - - let rpcStream = RPCStream( - descriptor: descriptor, - inbound: RPCAsyncSequence(wrapping: inbound), - outbound: RPCWriter.Closable( - wrapping: ServerConnection.Stream.Outbound( - responseWriter: outbound, - http2Stream: stream - ) - ) - ) - - let context = ServerContext(descriptor: descriptor) - await streamHandler(rpcStream, context) - } - } - - package func beginGracefulShutdown() { - self.serverQuiescingHelper.initiateShutdown(promise: nil) - } -} diff --git a/Sources/GRPCHTTP2Core/Server/Connection/GRPCServerFlushNotificationHandler.swift b/Sources/GRPCHTTP2Core/Server/Connection/GRPCServerFlushNotificationHandler.swift deleted file mode 100644 index 1bbffc205..000000000 --- a/Sources/GRPCHTTP2Core/Server/Connection/GRPCServerFlushNotificationHandler.swift +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -internal import NIOCore - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -final class GRPCServerFlushNotificationHandler: ChannelOutboundHandler { - typealias OutboundIn = Any - typealias OutboundOut = Any - - private let serverConnectionManagementHandler: ServerConnectionManagementHandler - - init( - serverConnectionManagementHandler: ServerConnectionManagementHandler - ) { - self.serverConnectionManagementHandler = serverConnectionManagementHandler - } - - func flush(context: ChannelHandlerContext) { - self.serverConnectionManagementHandler.syncView.connectionWillFlush() - context.flush() - } -} diff --git a/Sources/GRPCHTTP2Core/Server/Connection/ServerConnection.swift b/Sources/GRPCHTTP2Core/Server/Connection/ServerConnection.swift deleted file mode 100644 index 84d1d25a2..000000000 --- a/Sources/GRPCHTTP2Core/Server/Connection/ServerConnection.swift +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -package import GRPCCore -package import NIOCore - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -public enum ServerConnection { - public enum Stream { - package struct Outbound: ClosableRPCWriterProtocol { - package typealias Element = RPCResponsePart - - private let responseWriter: NIOAsyncChannelOutboundWriter - private let http2Stream: NIOAsyncChannel - - package init( - responseWriter: NIOAsyncChannelOutboundWriter, - http2Stream: NIOAsyncChannel - ) { - self.responseWriter = responseWriter - self.http2Stream = http2Stream - } - - package func write(_ element: RPCResponsePart) async throws { - try await self.responseWriter.write(element) - } - - package func write(contentsOf elements: some Sequence) async throws { - try await self.responseWriter.write(contentsOf: elements) - } - - package func finish() { - self.responseWriter.finish() - } - - package func finish(throwing error: any Error) { - // Fire the error inbound; this fails the inbound writer. - self.http2Stream.channel.pipeline.fireErrorCaught(error) - } - } - } -} diff --git a/Sources/GRPCHTTP2Core/Server/Connection/ServerConnectionManagementHandler+StateMachine.swift b/Sources/GRPCHTTP2Core/Server/Connection/ServerConnectionManagementHandler+StateMachine.swift deleted file mode 100644 index 8ca7660d6..000000000 --- a/Sources/GRPCHTTP2Core/Server/Connection/ServerConnectionManagementHandler+StateMachine.swift +++ /dev/null @@ -1,395 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -internal import NIOCore -internal import NIOHTTP2 - -extension ServerConnectionManagementHandler { - /// Tracks the state of TCP connections at the server. - /// - /// The state machine manages the state for the graceful shutdown procedure as well as policing - /// client-side keep alive. - struct StateMachine { - /// Current state. - private var state: State - - /// Opaque data sent to the client in a PING frame after emitting the first GOAWAY frame - /// as part of graceful shutdown. - private let goAwayPingData: HTTP2PingData - - /// Create a new state machine. - /// - /// - Parameters: - /// - allowKeepaliveWithoutCalls: Whether the client is permitted to send keep alive pings - /// when there are no active calls. - /// - minPingReceiveIntervalWithoutCalls: The minimum time interval required between keep - /// alive pings when there are no active calls. - /// - goAwayPingData: Opaque data sent to the client in a PING frame when the server - /// initiates graceful shutdown. - init( - allowKeepaliveWithoutCalls: Bool, - minPingReceiveIntervalWithoutCalls: TimeAmount, - goAwayPingData: HTTP2PingData = HTTP2PingData(withInteger: .random(in: .min ... .max)) - ) { - let keepalive = Keepalive( - allowWithoutCalls: allowKeepaliveWithoutCalls, - minPingReceiveIntervalWithoutCalls: minPingReceiveIntervalWithoutCalls - ) - - self.state = .active(State.Active(keepalive: keepalive)) - self.goAwayPingData = goAwayPingData - } - - /// Record that the stream with the given ID has been opened. - mutating func streamOpened(_ id: HTTP2StreamID) { - switch self.state { - case .active(var state): - self.state = ._modifying - state.lastStreamID = id - let (inserted, _) = state.openStreams.insert(id) - assert(inserted, "Can't open stream \(Int(id)), it's already open") - self.state = .active(state) - - case .closing(var state): - self.state = ._modifying - state.lastStreamID = id - let (inserted, _) = state.openStreams.insert(id) - assert(inserted, "Can't open stream \(Int(id)), it's already open") - self.state = .closing(state) - - case .closed: - () - - case ._modifying: - preconditionFailure() - } - } - - enum OnStreamClosed: Equatable { - /// Start the idle timer, after which the connection should be closed gracefully. - case startIdleTimer - /// Close the connection. - case close - /// Do nothing. - case none - } - - /// Record that the stream with the given ID has been closed. - mutating func streamClosed(_ id: HTTP2StreamID) -> OnStreamClosed { - let onStreamClosed: OnStreamClosed - - switch self.state { - case .active(var state): - self.state = ._modifying - let removedID = state.openStreams.remove(id) - assert(removedID != nil, "Can't close stream \(Int(id)), it wasn't open") - onStreamClosed = state.openStreams.isEmpty ? .startIdleTimer : .none - self.state = .active(state) - - case .closing(var state): - self.state = ._modifying - let removedID = state.openStreams.remove(id) - assert(removedID != nil, "Can't close stream \(Int(id)), it wasn't open") - // If the second GOAWAY hasn't been sent it isn't safe to close if there are no open - // streams: the client may have opened a stream which the server doesn't know about yet. - let canClose = state.sentSecondGoAway && state.openStreams.isEmpty - onStreamClosed = canClose ? .close : .none - self.state = .closing(state) - - case .closed: - onStreamClosed = .none - - case ._modifying: - preconditionFailure() - } - - return onStreamClosed - } - - enum OnPing: Equatable { - /// Send a GOAWAY frame with the code "enhance your calm" and immediately close the connection. - case enhanceYourCalmThenClose(HTTP2StreamID) - /// Acknowledge the ping. - case sendAck - /// Ignore the ping. - case none - } - - /// Received a ping with the given data. - /// - /// - Parameters: - /// - time: The time at which the ping was received. - /// - data: The data sent with the ping. - mutating func receivedPing(atTime time: NIODeadline, data: HTTP2PingData) -> OnPing { - let onPing: OnPing - - switch self.state { - case .active(var state): - self.state = ._modifying - let tooManyPings = state.keepalive.receivedPing( - atTime: time, - hasOpenStreams: !state.openStreams.isEmpty - ) - - if tooManyPings { - onPing = .enhanceYourCalmThenClose(state.lastStreamID) - self.state = .closed - } else { - onPing = .sendAck - self.state = .active(state) - } - - case .closing(var state): - self.state = ._modifying - let tooManyPings = state.keepalive.receivedPing( - atTime: time, - hasOpenStreams: !state.openStreams.isEmpty - ) - - if tooManyPings { - onPing = .enhanceYourCalmThenClose(state.lastStreamID) - self.state = .closed - } else { - onPing = .sendAck - self.state = .closing(state) - } - - case .closed: - onPing = .none - - case ._modifying: - preconditionFailure() - } - - return onPing - } - - enum OnPingAck: Equatable { - /// Send a GOAWAY frame with no error and the given last stream ID, optionally closing the - /// connection immediately afterwards. - case sendGoAway(lastStreamID: HTTP2StreamID, close: Bool) - /// Ignore the ack. - case none - } - - /// Received a PING frame with the 'ack' flag set. - mutating func receivedPingAck(data: HTTP2PingData) -> OnPingAck { - let onPingAck: OnPingAck - - switch self.state { - case .closing(var state): - self.state = ._modifying - - // If only one GOAWAY has been sent and the data matches the data from the GOAWAY ping then - // the server should send another GOAWAY ratcheting down the last stream ID. If no streams - // are open then the server can close the connection immediately after, otherwise it must - // wait until all streams are closed. - if !state.sentSecondGoAway, data == self.goAwayPingData { - state.sentSecondGoAway = true - - if state.openStreams.isEmpty { - self.state = .closed - onPingAck = .sendGoAway(lastStreamID: state.lastStreamID, close: true) - } else { - self.state = .closing(state) - onPingAck = .sendGoAway(lastStreamID: state.lastStreamID, close: false) - } - } else { - onPingAck = .none - } - - self.state = .closing(state) - - case .active, .closed: - onPingAck = .none - - case ._modifying: - preconditionFailure() - } - - return onPingAck - } - - enum OnStartGracefulShutdown: Equatable { - /// Initiate graceful shutdown by sending a GOAWAY frame with the last stream ID set as the max - /// stream ID and no error. Follow it immediately with a PING frame with the given data. - case sendGoAwayAndPing(HTTP2PingData) - /// Ignore the request to start graceful shutdown. - case none - } - - /// Request that the connection begins graceful shutdown. - mutating func startGracefulShutdown() -> OnStartGracefulShutdown { - let onStartGracefulShutdown: OnStartGracefulShutdown - - switch self.state { - case .active(let state): - self.state = .closing(State.Closing(from: state)) - onStartGracefulShutdown = .sendGoAwayAndPing(self.goAwayPingData) - - case .closing, .closed: - onStartGracefulShutdown = .none - - case ._modifying: - preconditionFailure() - } - - return onStartGracefulShutdown - } - - /// Reset the state of keep-alive policing. - mutating func resetKeepaliveState() { - switch self.state { - case .active(var state): - self.state = ._modifying - state.keepalive.reset() - self.state = .active(state) - - case .closing(var state): - self.state = ._modifying - state.keepalive.reset() - self.state = .closing(state) - - case .closed: - () - - case ._modifying: - preconditionFailure() - } - } - - /// Marks the state as closed. - mutating func markClosed() { - self.state = .closed - } - } -} - -extension ServerConnectionManagementHandler.StateMachine { - fileprivate struct Keepalive { - /// Allow the client to send keep alive pings when there are no active calls. - private let allowWithoutCalls: Bool - - /// The minimum time interval which pings may be received at when there are no active calls. - private let minPingReceiveIntervalWithoutCalls: TimeAmount - - /// The maximum number of "bad" pings sent by the client the server tolerates before closing - /// the connection. - private let maxPingStrikes: Int - - /// The number of "bad" pings sent by the client. This can be reset when the server sends - /// DATA or HEADERS frames. - /// - /// Ping strikes account for pings being occasionally being used for purposes other than keep - /// alive (a low number of strikes is therefore expected and okay). - private var pingStrikes: Int - - /// The last time a valid ping happened. - /// - /// Note: `distantPast` isn't used to indicate no previous valid ping as `NIODeadline` uses - /// the monotonic clock on Linux which uses an undefined starting point and in some cases isn't - /// always that distant. - private var lastValidPingTime: NIODeadline? - - init(allowWithoutCalls: Bool, minPingReceiveIntervalWithoutCalls: TimeAmount) { - self.allowWithoutCalls = allowWithoutCalls - self.minPingReceiveIntervalWithoutCalls = minPingReceiveIntervalWithoutCalls - self.maxPingStrikes = 2 - self.pingStrikes = 0 - self.lastValidPingTime = nil - } - - /// Reset ping strikes and the time of the last valid ping. - mutating func reset() { - self.lastValidPingTime = nil - self.pingStrikes = 0 - } - - /// Returns whether the client has sent too many pings. - mutating func receivedPing(atTime time: NIODeadline, hasOpenStreams: Bool) -> Bool { - let interval: TimeAmount - - if hasOpenStreams || self.allowWithoutCalls { - interval = self.minPingReceiveIntervalWithoutCalls - } else { - // If there are no open streams and keep alive pings aren't allowed without calls then - // use an interval of two hours. - // - // This comes from gRFC A8: https://github.com/grpc/proposal/blob/master/A8-client-side-keepalive.md - interval = .hours(2) - } - - // If there's no last ping time then the first is acceptable. - let isAcceptablePing = self.lastValidPingTime.map { $0 + interval <= time } ?? true - let tooManyPings: Bool - - if isAcceptablePing { - self.lastValidPingTime = time - tooManyPings = false - } else { - self.pingStrikes += 1 - tooManyPings = self.pingStrikes > self.maxPingStrikes - } - - return tooManyPings - } - } -} - -extension ServerConnectionManagementHandler.StateMachine { - fileprivate enum State { - /// The connection is active. - struct Active { - /// The number of open streams. - var openStreams: Set - /// The ID of the most recently opened stream (zero indicates no streams have been opened yet). - var lastStreamID: HTTP2StreamID - /// The state of keep alive. - var keepalive: Keepalive - - init(keepalive: Keepalive) { - self.openStreams = [] - self.lastStreamID = .rootStream - self.keepalive = keepalive - } - } - - /// The connection is closing gracefully, an initial GOAWAY frame has been sent (with the - /// last stream ID set to max). - struct Closing { - /// The number of open streams. - var openStreams: Set - /// The ID of the most recently opened stream (zero indicates no streams have been opened yet). - var lastStreamID: HTTP2StreamID - /// The state of keep alive. - var keepalive: Keepalive - /// Whether the second GOAWAY frame has been sent with a lower stream ID. - var sentSecondGoAway: Bool - - init(from state: Active) { - self.openStreams = state.openStreams - self.lastStreamID = state.lastStreamID - self.keepalive = state.keepalive - self.sentSecondGoAway = false - } - } - - case active(Active) - case closing(Closing) - case closed - case _modifying - } -} diff --git a/Sources/GRPCHTTP2Core/Server/Connection/ServerConnectionManagementHandler.swift b/Sources/GRPCHTTP2Core/Server/Connection/ServerConnectionManagementHandler.swift deleted file mode 100644 index 3ceee927b..000000000 --- a/Sources/GRPCHTTP2Core/Server/Connection/ServerConnectionManagementHandler.swift +++ /dev/null @@ -1,556 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -internal import GRPCCore -internal import NIOCore -internal import NIOHTTP2 -internal import NIOTLS - -/// A `ChannelHandler` which manages the lifecycle of a gRPC connection over HTTP/2. -/// -/// This handler is responsible for managing several aspects of the connection. These include: -/// 1. Handling the graceful close of connections. When gracefully closing a connection the server -/// sends a GOAWAY frame with the last stream ID set to the maximum stream ID allowed followed by -/// a PING frame. On receipt of the PING frame the server sends another GOAWAY frame with the -/// highest ID of all streams which have been opened. After this, the handler closes the -/// connection once all streams are closed. -/// 2. Enforcing that graceful shutdown doesn't exceed a configured limit (if configured). -/// 3. Gracefully closing the connection once it reaches the maximum configured age (if configured). -/// 4. Gracefully closing the connection once it has been idle for a given period of time (if -/// configured). -/// 5. Periodically sending keep alive pings to the client (if configured) and closing the -/// connection if necessary. -/// 6. Policing pings sent by the client to ensure that the client isn't misconfigured to send -/// too many pings. -/// -/// Some of the behaviours are described in: -/// - [gRFC A8](https://github.com/grpc/proposal/blob/master/A8-client-side-keepalive.md), and -/// - [gRFC A9](https://github.com/grpc/proposal/blob/master/A9-server-side-conn-mgt.md). -final class ServerConnectionManagementHandler: ChannelDuplexHandler { - typealias InboundIn = HTTP2Frame - typealias InboundOut = HTTP2Frame - typealias OutboundIn = HTTP2Frame - typealias OutboundOut = HTTP2Frame - - /// The `EventLoop` of the `Channel` this handler exists in. - private let eventLoop: any EventLoop - - /// The maximum amount of time a connection may be idle for. If the connection remains idle - /// (i.e. has no open streams) for this period of time then the connection will be gracefully - /// closed. - private var maxIdleTimer: Timer? - - /// The maximum age of a connection. If the connection remains open after this amount of time - /// then it will be gracefully closed. - private var maxAgeTimer: Timer? - - /// The maximum amount of time a connection may spend closing gracefully, after which it is - /// closed abruptly. The timer starts after the second GOAWAY frame has been sent. - private var maxGraceTimer: Timer? - - /// The amount of time to wait before sending a keep alive ping. - private var keepaliveTimer: Timer? - - /// The amount of time the client has to reply after sending a keep alive ping. Only used if - /// `keepaliveTimer` is set. - private var keepaliveTimeoutTimer: Timer - - /// Opaque data sent in keep alive pings. - private let keepalivePingData: HTTP2PingData - - /// Whether a flush is pending. - private var flushPending: Bool - - /// Whether `channelRead` has been called and `channelReadComplete` hasn't yet been called. - /// Resets once `channelReadComplete` returns. - private var inReadLoop: Bool - - /// The context of the channel this handler is in. - private var context: ChannelHandlerContext? - - /// The current state of the connection. - private var state: StateMachine - - /// The clock. - private let clock: Clock - - /// Whether ALPN is required. - /// If it is but the TLS handshake finished without negotiating a protocol, an error will be fired down the - /// pipeline and the channel will be closed. - private let requireALPN: Bool - - /// A clock providing the current time. - /// - /// This is necessary for testing where a manual clock can be used and advanced from the test. - /// While NIO's `EmbeddedEventLoop` provides control over its view of time (and therefore any - /// events scheduled on it) it doesn't offer a way to get the current time. This is usually done - /// via `NIODeadline`. - enum Clock { - case nio - case manual(Manual) - - func now() -> NIODeadline { - switch self { - case .nio: - return .now() - case .manual(let clock): - return clock.time - } - } - - final class Manual { - private(set) var time: NIODeadline - - init() { - self.time = .uptimeNanoseconds(0) - } - - func advance(by amount: TimeAmount) { - self.time = self.time + amount - } - } - } - - /// Stats about recently written frames. Used to determine whether to reset keep-alive state. - private var frameStats: FrameStats - - struct FrameStats { - private(set) var didWriteHeadersOrData = false - - /// Mark that a HEADERS frame has been written. - mutating func wroteHeaders() { - self.didWriteHeadersOrData = true - } - - /// Mark that DATA frame has been written. - mutating func wroteData() { - self.didWriteHeadersOrData = true - } - - /// Resets the state such that no HEADERS or DATA frames have been written. - mutating func reset() { - self.didWriteHeadersOrData = false - } - } - - /// A synchronous view over this handler. - var syncView: SyncView { - return SyncView(self) - } - - /// A synchronous view over this handler. - /// - /// Methods on this view *must* be called from the same `EventLoop` as the `Channel` in which - /// this handler exists. - struct SyncView { - private let handler: ServerConnectionManagementHandler - - fileprivate init(_ handler: ServerConnectionManagementHandler) { - self.handler = handler - } - - /// Notify the handler that the connection has received a flush event. - func connectionWillFlush() { - // The handler can't rely on `flush(context:)` due to its expected position in the pipeline. - // It's expected to be placed after the HTTP/2 handler (i.e. closer to the application) as - // it needs to receive HTTP/2 frames. However, flushes from stream channels aren't sent down - // the entire connection channel, instead they are sent from the point in the channel they - // are multiplexed from (either the HTTP/2 handler or the HTTP/2 multiplexing handler, - // depending on how multiplexing is configured). - self.handler.eventLoop.assertInEventLoop() - if self.handler.frameStats.didWriteHeadersOrData { - self.handler.frameStats.reset() - self.handler.state.resetKeepaliveState() - } - } - - /// Notify the handler that a HEADERS frame was written in the last write loop. - func wroteHeadersFrame() { - self.handler.eventLoop.assertInEventLoop() - self.handler.frameStats.wroteHeaders() - } - - /// Notify the handler that a DATA frame was written in the last write loop. - func wroteDataFrame() { - self.handler.eventLoop.assertInEventLoop() - self.handler.frameStats.wroteData() - } - } - - /// Creates a new handler which manages the lifecycle of a connection. - /// - /// - Parameters: - /// - eventLoop: The `EventLoop` of the `Channel` this handler is placed in. - /// - maxIdleTime: The maximum amount time a connection may be idle for before being closed. - /// - maxAge: The maximum amount of time a connection may exist before being gracefully closed. - /// - maxGraceTime: The maximum amount of time that the connection has to close gracefully. - /// - keepaliveTime: The amount of time to wait after reading data before sending a keep-alive - /// ping. - /// - keepaliveTimeout: The amount of time the client has to reply after the server sends a - /// keep-alive ping to keep the connection open. The connection is closed if no reply - /// is received. - /// - allowKeepaliveWithoutCalls: Whether the server allows the client to send keep-alive pings - /// when there are no calls in progress. - /// - minPingIntervalWithoutCalls: The minimum allowed interval the client is allowed to send - /// keep-alive pings. Pings more frequent than this interval count as 'strikes' and the - /// connection is closed if there are too many strikes. - /// - clock: A clock providing the current time. - init( - eventLoop: any EventLoop, - maxIdleTime: TimeAmount?, - maxAge: TimeAmount?, - maxGraceTime: TimeAmount?, - keepaliveTime: TimeAmount?, - keepaliveTimeout: TimeAmount?, - allowKeepaliveWithoutCalls: Bool, - minPingIntervalWithoutCalls: TimeAmount, - requireALPN: Bool, - clock: Clock = .nio - ) { - self.eventLoop = eventLoop - - self.maxIdleTimer = maxIdleTime.map { Timer(delay: $0) } - self.maxAgeTimer = maxAge.map { Timer(delay: $0) } - self.maxGraceTimer = maxGraceTime.map { Timer(delay: $0) } - - self.keepaliveTimer = keepaliveTime.map { Timer(delay: $0) } - // Always create a keep alive timeout timer, it's only used if there is a keep alive timer. - self.keepaliveTimeoutTimer = Timer(delay: keepaliveTimeout ?? .seconds(20)) - - // Generate a random value to be used as keep alive ping data. - let pingData = UInt64.random(in: .min ... .max) - self.keepalivePingData = HTTP2PingData(withInteger: pingData) - - self.state = StateMachine( - allowKeepaliveWithoutCalls: allowKeepaliveWithoutCalls, - minPingReceiveIntervalWithoutCalls: minPingIntervalWithoutCalls, - goAwayPingData: HTTP2PingData(withInteger: ~pingData) - ) - - self.flushPending = false - self.inReadLoop = false - self.clock = clock - self.frameStats = FrameStats() - - self.requireALPN = requireALPN - } - - func handlerAdded(context: ChannelHandlerContext) { - assert(context.eventLoop === self.eventLoop) - self.context = context - } - - func handlerRemoved(context: ChannelHandlerContext) { - self.context = nil - } - - func channelActive(context: ChannelHandlerContext) { - let view = LoopBoundView(handler: self, context: context) - - self.maxAgeTimer?.schedule(on: context.eventLoop) { - view.initiateGracefulShutdown() - } - - self.maxIdleTimer?.schedule(on: context.eventLoop) { - view.initiateGracefulShutdown() - } - - self.keepaliveTimer?.schedule(on: context.eventLoop) { - view.keepaliveTimerFired() - } - - context.fireChannelActive() - } - - func channelInactive(context: ChannelHandlerContext) { - self.maxIdleTimer?.cancel() - self.maxAgeTimer?.cancel() - self.maxGraceTimer?.cancel() - self.keepaliveTimer?.cancel() - self.keepaliveTimeoutTimer.cancel() - context.fireChannelInactive() - } - - func userInboundEventTriggered(context: ChannelHandlerContext, event: Any) { - switch event { - case let event as NIOHTTP2StreamCreatedEvent: - self._streamCreated(event.streamID, channel: context.channel) - - case let event as StreamClosedEvent: - self._streamClosed(event.streamID, channel: context.channel) - - case is ChannelShouldQuiesceEvent: - self.initiateGracefulShutdown(context: context) - - case TLSUserEvent.handshakeCompleted(let negotiatedProtocol): - if negotiatedProtocol == nil, self.requireALPN { - // No ALPN protocol negotiated but it was required: fire an error and close the channel. - context.fireErrorCaught( - RPCError( - code: .internalError, - message: "ALPN resulted in no protocol being negotiated, but it was required." - ) - ) - context.close(mode: .all, promise: nil) - } - - default: - () - } - - context.fireUserInboundEventTriggered(event) - } - - func channelRead(context: ChannelHandlerContext, data: NIOAny) { - self.inReadLoop = true - - // Any read data indicates that the connection is alive so cancel the keep-alive timers. - self.keepaliveTimer?.cancel() - self.keepaliveTimeoutTimer.cancel() - - let frame = self.unwrapInboundIn(data) - switch frame.payload { - case .ping(let data, let ack): - if ack { - self.handlePingAck(context: context, data: data) - } else { - self.handlePing(context: context, data: data) - } - - default: - () // Only interested in PING frames, ignore the rest. - } - - context.fireChannelRead(data) - } - - func channelReadComplete(context: ChannelHandlerContext) { - while self.flushPending { - self.flushPending = false - context.flush() - } - - self.inReadLoop = false - - // Done reading: schedule the keep-alive timer. - let view = LoopBoundView(handler: self, context: context) - self.keepaliveTimer?.schedule(on: context.eventLoop) { - view.keepaliveTimerFired() - } - - context.fireChannelReadComplete() - } - - func flush(context: ChannelHandlerContext) { - self.maybeFlush(context: context) - } -} - -extension ServerConnectionManagementHandler { - struct LoopBoundView: @unchecked Sendable { - private let handler: ServerConnectionManagementHandler - private let context: ChannelHandlerContext - - init(handler: ServerConnectionManagementHandler, context: ChannelHandlerContext) { - self.handler = handler - self.context = context - } - - func initiateGracefulShutdown() { - self.context.eventLoop.assertInEventLoop() - self.handler.initiateGracefulShutdown(context: self.context) - } - - func keepaliveTimerFired() { - self.context.eventLoop.assertInEventLoop() - self.handler.keepaliveTimerFired(context: self.context) - } - - } -} - -extension ServerConnectionManagementHandler { - struct HTTP2StreamDelegate: @unchecked Sendable, NIOHTTP2StreamDelegate { - // @unchecked is okay: the only methods do the appropriate event-loop dance. - - private let handler: ServerConnectionManagementHandler - - init(_ handler: ServerConnectionManagementHandler) { - self.handler = handler - } - - func streamCreated(_ id: HTTP2StreamID, channel: any Channel) { - if self.handler.eventLoop.inEventLoop { - self.handler._streamCreated(id, channel: channel) - } else { - self.handler.eventLoop.execute { - self.handler._streamCreated(id, channel: channel) - } - } - } - - func streamClosed(_ id: HTTP2StreamID, channel: any Channel) { - if self.handler.eventLoop.inEventLoop { - self.handler._streamClosed(id, channel: channel) - } else { - self.handler.eventLoop.execute { - self.handler._streamClosed(id, channel: channel) - } - } - } - } - - var http2StreamDelegate: HTTP2StreamDelegate { - return HTTP2StreamDelegate(self) - } - - private func _streamCreated(_ id: HTTP2StreamID, channel: any Channel) { - // The connection isn't idle if a stream is open. - self.maxIdleTimer?.cancel() - self.state.streamOpened(id) - } - - private func _streamClosed(_ id: HTTP2StreamID, channel: any Channel) { - guard let context = self.context else { return } - - switch self.state.streamClosed(id) { - case .startIdleTimer: - let loopBound = LoopBoundView(handler: self, context: context) - self.maxIdleTimer?.schedule(on: context.eventLoop) { - loopBound.initiateGracefulShutdown() - } - - case .close: - context.close(mode: .all, promise: nil) - - case .none: - () - } - } -} - -extension ServerConnectionManagementHandler { - private func maybeFlush(context: ChannelHandlerContext) { - if self.inReadLoop { - self.flushPending = true - } else { - context.flush() - } - } - - private func initiateGracefulShutdown(context: ChannelHandlerContext) { - context.eventLoop.assertInEventLoop() - - // Cancel any timers if initiating shutdown. - self.maxIdleTimer?.cancel() - self.maxAgeTimer?.cancel() - self.keepaliveTimer?.cancel() - self.keepaliveTimeoutTimer.cancel() - - switch self.state.startGracefulShutdown() { - case .sendGoAwayAndPing(let pingData): - // There's a time window between the server sending a GOAWAY frame and the client receiving - // it. During this time the client may open new streams as it doesn't yet know about the - // GOAWAY frame. - // - // The server therefore sends a GOAWAY with the last stream ID set to the maximum stream ID - // and follows it with a PING frame. When the server receives the ack for the PING frame it - // knows that the client has received the initial GOAWAY frame and that no more streams may - // be opened. The server can then send an additional GOAWAY frame with a more representative - // last stream ID. - let goAway = HTTP2Frame( - streamID: .rootStream, - payload: .goAway( - lastStreamID: .maxID, - errorCode: .noError, - opaqueData: nil - ) - ) - - let ping = HTTP2Frame(streamID: .rootStream, payload: .ping(pingData, ack: false)) - - context.write(self.wrapOutboundOut(goAway), promise: nil) - context.write(self.wrapOutboundOut(ping), promise: nil) - self.maybeFlush(context: context) - - case .none: - () // Already shutting down. - } - } - - private func handlePing(context: ChannelHandlerContext, data: HTTP2PingData) { - switch self.state.receivedPing(atTime: self.clock.now(), data: data) { - case .enhanceYourCalmThenClose(let streamID): - let goAway = HTTP2Frame( - streamID: .rootStream, - payload: .goAway( - lastStreamID: streamID, - errorCode: .enhanceYourCalm, - opaqueData: context.channel.allocator.buffer(string: "too_many_pings") - ) - ) - - context.write(self.wrapOutboundOut(goAway), promise: nil) - self.maybeFlush(context: context) - context.close(promise: nil) - - case .sendAck: - () // ACKs are sent by NIO's HTTP/2 handler, don't double ack. - - case .none: - () - } - } - - private func handlePingAck(context: ChannelHandlerContext, data: HTTP2PingData) { - switch self.state.receivedPingAck(data: data) { - case .sendGoAway(let streamID, let close): - let goAway = HTTP2Frame( - streamID: .rootStream, - payload: .goAway(lastStreamID: streamID, errorCode: .noError, opaqueData: nil) - ) - - context.write(self.wrapOutboundOut(goAway), promise: nil) - self.maybeFlush(context: context) - - if close { - context.close(promise: nil) - } else { - // RPCs may have a grace period for finishing once the second GOAWAY frame has finished. - // If this is set close the connection abruptly once the grace period passes. - let loopBound = NIOLoopBound(context, eventLoop: context.eventLoop) - self.maxGraceTimer?.schedule(on: context.eventLoop) { - loopBound.value.close(promise: nil) - } - } - - case .none: - () - } - } - - private func keepaliveTimerFired(context: ChannelHandlerContext) { - let ping = HTTP2Frame(streamID: .rootStream, payload: .ping(self.keepalivePingData, ack: false)) - context.write(self.wrapInboundOut(ping), promise: nil) - self.maybeFlush(context: context) - - // Schedule a timeout on waiting for the response. - let loopBound = LoopBoundView(handler: self, context: context) - self.keepaliveTimeoutTimer.schedule(on: context.eventLoop) { - loopBound.initiateGracefulShutdown() - } - } -} diff --git a/Sources/GRPCHTTP2Core/Server/GRPCServerStreamHandler.swift b/Sources/GRPCHTTP2Core/Server/GRPCServerStreamHandler.swift deleted file mode 100644 index 54965cf13..000000000 --- a/Sources/GRPCHTTP2Core/Server/GRPCServerStreamHandler.swift +++ /dev/null @@ -1,291 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -package import GRPCCore -package import NIOCore -package import NIOHTTP2 - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -package final class GRPCServerStreamHandler: ChannelDuplexHandler, RemovableChannelHandler { - package typealias InboundIn = HTTP2Frame.FramePayload - package typealias InboundOut = RPCRequestPart - - package typealias OutboundIn = RPCResponsePart - package typealias OutboundOut = HTTP2Frame.FramePayload - - private var stateMachine: GRPCStreamStateMachine - - private var isReading = false - private var flushPending = false - - // We buffer the final status + trailers to avoid reordering issues (i.e., - // if there are messages still not written into the channel because flush has - // not been called, but the server sends back trailers). - private var pendingTrailers: - (trailers: HTTP2Frame.FramePayload, promise: EventLoopPromise?)? - - private let methodDescriptorPromise: EventLoopPromise - - // Existential errors unconditionally allocate, avoid this per-use allocation by doing it - // statically. - private static let handlerRemovedBeforeDescriptorResolved: any Error = RPCError( - code: .unavailable, - message: "RPC stream was closed before we got any Metadata." - ) - - package init( - scheme: Scheme, - acceptedEncodings: CompressionAlgorithmSet, - maxPayloadSize: Int, - methodDescriptorPromise: EventLoopPromise, - skipStateMachineAssertions: Bool = false - ) { - self.stateMachine = .init( - configuration: .server(.init(scheme: scheme, acceptedEncodings: acceptedEncodings)), - maxPayloadSize: maxPayloadSize, - skipAssertions: skipStateMachineAssertions - ) - self.methodDescriptorPromise = methodDescriptorPromise - } -} - -// - MARK: ChannelInboundHandler - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -extension GRPCServerStreamHandler { - package func channelRead(context: ChannelHandlerContext, data: NIOAny) { - self.isReading = true - let frame = self.unwrapInboundIn(data) - switch frame { - case .data(let frameData): - let endStream = frameData.endStream - switch frameData.data { - case .byteBuffer(let buffer): - do { - switch try self.stateMachine.receive(buffer: buffer, endStream: endStream) { - case .endRPCAndForwardErrorStatus_clientOnly: - preconditionFailure( - "OnBufferReceivedAction.endRPCAndForwardErrorStatus should never be returned for the server." - ) - - case .forwardErrorAndClose_serverOnly(let error): - context.fireErrorCaught(error) - context.close(mode: .all, promise: nil) - - case .readInbound: - loop: while true { - switch self.stateMachine.nextInboundMessage() { - case .receiveMessage(let message): - context.fireChannelRead(self.wrapInboundOut(.message(message))) - case .awaitMoreMessages: - break loop - case .noMoreMessages: - context.fireUserInboundEventTriggered(ChannelEvent.inputClosed) - break loop - } - } - case .doNothing: - () - } - } catch let invalidState { - let error = RPCError(invalidState) - context.fireErrorCaught(error) - } - - case .fileRegion: - preconditionFailure("Unexpected IOData.fileRegion") - } - - case .headers(let headers): - do { - let action = try self.stateMachine.receive( - headers: headers.headers, - endStream: headers.endStream - ) - switch action { - case .receivedMetadata(let metadata, let methodDescriptor): - if let methodDescriptor = methodDescriptor { - self.methodDescriptorPromise.succeed(methodDescriptor) - context.fireChannelRead(self.wrapInboundOut(.metadata(metadata))) - } else { - assertionFailure("Method descriptor should have been present if we received metadata.") - } - - case .rejectRPC_serverOnly(let trailers): - self.flushPending = true - self.methodDescriptorPromise.fail( - RPCError( - code: .unavailable, - message: "RPC was rejected." - ) - ) - let response = HTTP2Frame.FramePayload.headers(.init(headers: trailers, endStream: true)) - context.write(self.wrapOutboundOut(response), promise: nil) - - case .receivedStatusAndMetadata_clientOnly: - assertionFailure("Unexpected action") - - case .protocolViolation_serverOnly: - context.writeAndFlush(self.wrapOutboundOut(.rstStream(.protocolError)), promise: nil) - context.close(promise: nil) - - case .doNothing: - () - } - } catch let invalidState { - let error = RPCError(invalidState) - context.fireErrorCaught(error) - } - - case .rstStream: - self.handleUnexpectedInboundClose(context: context, reason: .streamReset) - - case .ping, .goAway, .priority, .settings, .pushPromise, .windowUpdate, - .alternativeService, .origin: - () - } - } - - package func channelReadComplete(context: ChannelHandlerContext) { - self.isReading = false - if self.flushPending { - self.flushPending = false - context.flush() - } - context.fireChannelReadComplete() - } - - package func handlerRemoved(context: ChannelHandlerContext) { - self.stateMachine.tearDown() - self.methodDescriptorPromise.fail(Self.handlerRemovedBeforeDescriptorResolved) - } - - package func channelInactive(context: ChannelHandlerContext) { - self.handleUnexpectedInboundClose(context: context, reason: .channelInactive) - context.fireChannelInactive() - } - - package func errorCaught(context: ChannelHandlerContext, error: any Error) { - self.handleUnexpectedInboundClose(context: context, reason: .errorThrown(error)) - } - - private func handleUnexpectedInboundClose( - context: ChannelHandlerContext, - reason: GRPCStreamStateMachine.UnexpectedInboundCloseReason - ) { - switch self.stateMachine.unexpectedInboundClose(reason: reason) { - case .fireError_serverOnly(let wrappedError): - context.fireErrorCaught(wrappedError) - case .doNothing: - () - case .forwardStatus_clientOnly: - assertionFailure( - "`forwardStatus` should only happen on the client side, never on the server." - ) - } - } -} - -// - MARK: ChannelOutboundHandler - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -extension GRPCServerStreamHandler { - package func write( - context: ChannelHandlerContext, - data: NIOAny, - promise: EventLoopPromise? - ) { - let frame = self.unwrapOutboundIn(data) - switch frame { - case .metadata(let metadata): - do { - self.flushPending = true - let headers = try self.stateMachine.send(metadata: metadata) - context.write(self.wrapOutboundOut(.headers(.init(headers: headers))), promise: promise) - } catch let invalidState { - let error = RPCError(invalidState) - promise?.fail(error) - context.fireErrorCaught(error) - } - - case .message(let message): - do { - try self.stateMachine.send(message: message, promise: promise) - } catch let invalidState { - let error = RPCError(invalidState) - promise?.fail(error) - context.fireErrorCaught(error) - } - - case .status(let status, let metadata): - do { - let headers = try self.stateMachine.send(status: status, metadata: metadata) - let response = HTTP2Frame.FramePayload.headers(.init(headers: headers, endStream: true)) - self.pendingTrailers = (response, promise) - } catch let invalidState { - let error = RPCError(invalidState) - promise?.fail(error) - context.fireErrorCaught(error) - } - } - } - - package func flush(context: ChannelHandlerContext) { - if self.isReading { - // We don't want to flush yet if we're still in a read loop. - return - } - - do { - loop: while true { - switch try self.stateMachine.nextOutboundFrame() { - case .sendFrame(let byteBuffer, let promise): - self.flushPending = true - context.write( - self.wrapOutboundOut(.data(.init(data: .byteBuffer(byteBuffer)))), - promise: promise - ) - - case .noMoreMessages: - if let pendingTrailers = self.pendingTrailers { - self.flushPending = true - self.pendingTrailers = nil - context.write( - self.wrapOutboundOut(pendingTrailers.trailers), - promise: pendingTrailers.promise - ) - } - break loop - - case .awaitMoreMessages: - break loop - - case .closeAndFailPromise(let promise, let error): - context.close(mode: .all, promise: nil) - promise?.fail(error) - } - } - - if self.flushPending { - self.flushPending = false - context.flush() - } - } catch let invalidState { - let error = RPCError(invalidState) - context.fireErrorCaught(error) - } - } -} diff --git a/Sources/GRPCHTTP2Core/Server/HTTP2ListenerFactory.swift b/Sources/GRPCHTTP2Core/Server/HTTP2ListenerFactory.swift deleted file mode 100644 index 900799a61..000000000 --- a/Sources/GRPCHTTP2Core/Server/HTTP2ListenerFactory.swift +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -package import NIOCore -package import NIOExtras - -/// A factory to produce `NIOAsyncChannel`s to listen for new HTTP/2 connections. -/// -/// - SeeAlso: ``CommonHTTP2ServerTransport`` -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -package protocol HTTP2ListenerFactory: Sendable { - typealias AcceptedChannel = ( - ChannelPipeline.SynchronousOperations.HTTP2ConnectionChannel, - ChannelPipeline.SynchronousOperations.HTTP2StreamMultiplexer - ) - - func makeListeningChannel( - eventLoopGroup: any EventLoopGroup, - address: SocketAddress, - serverQuiescingHelper: ServerQuiescingHelper - ) async throws -> NIOAsyncChannel -} diff --git a/Sources/GRPCHTTP2Core/Server/HTTP2ServerTransport.swift b/Sources/GRPCHTTP2Core/Server/HTTP2ServerTransport.swift deleted file mode 100644 index e93b09c26..000000000 --- a/Sources/GRPCHTTP2Core/Server/HTTP2ServerTransport.swift +++ /dev/null @@ -1,191 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -public import GRPCCore -internal import NIOHTTP2 - -/// A namespace for the HTTP/2 server transport. -public enum HTTP2ServerTransport {} - -extension HTTP2ServerTransport { - /// A namespace for HTTP/2 server transport configuration. - public enum Config {} -} - -extension HTTP2ServerTransport.Config { - public struct Compression: Sendable, Hashable { - /// Compression algorithms enabled for inbound messages. - /// - /// - Note: `CompressionAlgorithm.none` is always supported, even if it isn't set here. - public var enabledAlgorithms: CompressionAlgorithmSet - - /// Creates a new compression configuration. - /// - /// - SeeAlso: ``defaults``. - public init(enabledAlgorithms: CompressionAlgorithmSet) { - self.enabledAlgorithms = enabledAlgorithms - } - - /// Default values, compression is disabled. - public static var defaults: Self { - Self(enabledAlgorithms: .none) - } - } - - @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) - public struct Keepalive: Sendable, Hashable { - /// The amount of time to wait after reading data before sending a keepalive ping. - public var time: Duration - - /// The amount of time the server has to respond to a keepalive ping before the connection is closed. - public var timeout: Duration - - /// Configuration for how the server enforces client keepalive. - public var clientBehavior: ClientKeepaliveBehavior - - /// Creates a new keepalive configuration. - public init( - time: Duration, - timeout: Duration, - clientBehavior: ClientKeepaliveBehavior - ) { - self.time = time - self.timeout = timeout - self.clientBehavior = clientBehavior - } - - /// Default values. The time after reading data a ping should be sent defaults to 2 hours, the timeout for - /// keepalive pings defaults to 20 seconds, pings are not permitted when no calls are in progress, and - /// the minimum allowed interval for clients to send pings defaults to 5 minutes. - public static var defaults: Self { - Self( - time: .seconds(2 * 60 * 60), // 2 hours - timeout: .seconds(20), - clientBehavior: .defaults - ) - } - } - - @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) - public struct ClientKeepaliveBehavior: Sendable, Hashable { - /// The minimum allowed interval the client is allowed to send keep-alive pings. - /// Pings more frequent than this interval count as 'strikes' and the connection is closed if there are - /// too many strikes. - public var minPingIntervalWithoutCalls: Duration - - /// Whether the server allows the client to send keepalive pings when there are no calls in progress. - public var allowWithoutCalls: Bool - - /// Creates a new configuration for permitted client keepalive behavior. - public init( - minPingIntervalWithoutCalls: Duration, - allowWithoutCalls: Bool - ) { - self.minPingIntervalWithoutCalls = minPingIntervalWithoutCalls - self.allowWithoutCalls = allowWithoutCalls - } - - /// Default values. The time after reading data a ping should be sent defaults to 2 hours, the timeout for - /// keepalive pings defaults to 20 seconds, pings are not permitted when no calls are in progress, and - /// the minimum allowed interval for clients to send pings defaults to 5 minutes. - public static var defaults: Self { - Self(minPingIntervalWithoutCalls: .seconds(5 * 60), allowWithoutCalls: false) - } - } - - @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) - public struct Connection: Sendable, Hashable { - /// The maximum amount of time a connection may exist before being gracefully closed. - public var maxAge: Duration? - - /// The maximum amount of time that the connection has to close gracefully. - public var maxGraceTime: Duration? - - /// The maximum amount of time a connection may be idle before it's closed. - public var maxIdleTime: Duration? - - /// Configuration for keepalive used to detect broken connections. - /// - /// - SeeAlso: gRFC A8 for client side keepalive, and gRFC A9 for server connection management. - public var keepalive: Keepalive - - public init( - maxAge: Duration?, - maxGraceTime: Duration?, - maxIdleTime: Duration?, - keepalive: Keepalive - ) { - self.maxAge = maxAge - self.maxGraceTime = maxGraceTime - self.maxIdleTime = maxIdleTime - self.keepalive = keepalive - } - - /// Default values. The max connection age, max grace time, and max idle time default to - /// `nil` (i.e. infinite). See ``HTTP2ServerTransport/Config/Keepalive/defaults`` for keepalive - /// defaults. - public static var defaults: Self { - Self(maxAge: nil, maxGraceTime: nil, maxIdleTime: nil, keepalive: .defaults) - } - } - - public struct HTTP2: Sendable, Hashable { - /// The maximum frame size to be used in an HTTP/2 connection. - public var maxFrameSize: Int - - /// The target window size for this connection. - /// - /// - Note: This will also be set as the initial window size for the connection. - public var targetWindowSize: Int - - /// The number of concurrent streams on the HTTP/2 connection. - public var maxConcurrentStreams: Int? - - public init( - maxFrameSize: Int, - targetWindowSize: Int, - maxConcurrentStreams: Int? - ) { - self.maxFrameSize = maxFrameSize - self.targetWindowSize = targetWindowSize - self.maxConcurrentStreams = maxConcurrentStreams - } - - /// Default values. The max frame size defaults to 2^14, the target window size defaults to 2^16-1, and - /// the max concurrent streams default to infinite. - public static var defaults: Self { - Self( - maxFrameSize: 1 << 14, - targetWindowSize: (1 << 16) - 1, - maxConcurrentStreams: nil - ) - } - } - - public struct RPC: Sendable, Hashable { - /// The maximum request payload size. - public var maxRequestPayloadSize: Int - - public init(maxRequestPayloadSize: Int) { - self.maxRequestPayloadSize = maxRequestPayloadSize - } - - /// Default values. Maximum request payload size defaults to 4MiB. - public static var defaults: Self { - Self(maxRequestPayloadSize: 4 * 1024 * 1024) - } - } -} diff --git a/Sources/GRPCHTTP2Transport/Exports.swift b/Sources/GRPCHTTP2Transport/Exports.swift deleted file mode 100644 index 64f679933..000000000 --- a/Sources/GRPCHTTP2Transport/Exports.swift +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -@_exported import GRPCCore -@_exported import GRPCHTTP2Core -@_exported import GRPCHTTP2TransportNIOPosix -@_exported import GRPCHTTP2TransportNIOTransportServices diff --git a/Sources/GRPCHTTP2TransportNIOPosix/Exports.swift b/Sources/GRPCHTTP2TransportNIOPosix/Exports.swift deleted file mode 100644 index 395308d46..000000000 --- a/Sources/GRPCHTTP2TransportNIOPosix/Exports.swift +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -@_exported import GRPCCore -@_exported import GRPCHTTP2Core diff --git a/Sources/GRPCHTTP2TransportNIOPosix/HTTP2ClientTransport+Posix.swift b/Sources/GRPCHTTP2TransportNIOPosix/HTTP2ClientTransport+Posix.swift deleted file mode 100644 index baf96639a..000000000 --- a/Sources/GRPCHTTP2TransportNIOPosix/HTTP2ClientTransport+Posix.swift +++ /dev/null @@ -1,298 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -public import GRPCCore -public import GRPCHTTP2Core // should be @usableFromInline -public import NIOCore // has to be public because of EventLoopGroup param in init -public import NIOPosix // has to be public because of default argument value in init - -#if canImport(NIOSSL) -private import NIOSSL -#endif - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension HTTP2ClientTransport { - /// A `ClientTransport` using HTTP/2 built on top of `NIOPosix`. - /// - /// This transport builds on top of SwiftNIO's Posix networking layer and is suitable for use - /// on Linux and Darwin based platforms (macOS, iOS, etc.). However, it's *strongly* recommended - /// that if you are targeting Darwin platforms then you should use the `NIOTS` variant of - /// the `HTTP2ClientTransport`. - /// - /// To use this transport you need to provide a 'target' to connect to which will be resolved - /// by an appropriate resolver from the resolver registry. By default the resolver registry can - /// resolve DNS targets, IPv4 and IPv6 targets, Unix domain socket targets, and Virtual Socket - /// targets. If you use a custom target you must also provide an appropriately configured - /// registry. - /// - /// You can control various aspects of connection creation, management, security and RPC behavior via - /// the ``Config``. Load balancing policies and other RPC specific behavior can be configured via - /// the `ServiceConfig` (if it isn't provided by a resolver). - /// - /// Beyond creating the transport you don't need to interact with it directly, instead, pass it - /// to a `GRPCClient`: - /// - /// ```swift - /// try await withThrowingDiscardingTaskGroup { group in - /// let transport = try HTTP2ClientTransport.Posix( - /// target: .ipv4(host: "example.com"), - /// config: .defaults(transportSecurity: .plaintext) - /// ) - /// let client = GRPCClient(transport: transport) - /// group.addTask { - /// try await client.run() - /// } - /// - /// // ... - /// } - /// ``` - public struct Posix: ClientTransport { - private let channel: GRPCChannel - - /// Creates a new NIOPosix-based HTTP/2 client transport. - /// - /// - Parameters: - /// - target: A target to resolve. - /// - config: Configuration for the transport. - /// - resolverRegistry: A registry of resolver factories. - /// - serviceConfig: Service config controlling how the transport should establish and - /// load-balance connections. - /// - eventLoopGroup: The underlying NIO `EventLoopGroup` to run connections on. This must - /// be a `MultiThreadedEventLoopGroup` or an `EventLoop` from - /// a `MultiThreadedEventLoopGroup`. - /// - Throws: When no suitable resolver could be found for the `target`. - public init( - target: any ResolvableTarget, - config: Config, - resolverRegistry: NameResolverRegistry = .defaults, - serviceConfig: ServiceConfig = ServiceConfig(), - eventLoopGroup: any EventLoopGroup = .singletonMultiThreadedEventLoopGroup - ) throws { - guard let resolver = resolverRegistry.makeResolver(for: target) else { - throw RuntimeError( - code: .transportError, - message: """ - No suitable resolvers to resolve '\(target)'. You must make sure that the resolver \ - registry has a suitable name resolver factory registered for the given target. - """ - ) - } - - self.channel = GRPCChannel( - resolver: resolver, - connector: try Connector(eventLoopGroup: eventLoopGroup, config: config), - config: GRPCChannel.Config(posix: config), - defaultServiceConfig: serviceConfig - ) - } - - public var retryThrottle: RetryThrottle? { - self.channel.retryThrottle - } - - public func connect() async { - await self.channel.connect() - } - - public func config(forMethod descriptor: MethodDescriptor) -> MethodConfig? { - self.channel.config(forMethod: descriptor) - } - - public func beginGracefulShutdown() { - self.channel.beginGracefulShutdown() - } - - public func withStream( - descriptor: MethodDescriptor, - options: CallOptions, - _ closure: (RPCStream) async throws -> T - ) async throws -> T { - try await self.channel.withStream(descriptor: descriptor, options: options, closure) - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension HTTP2ClientTransport.Posix { - struct Connector: HTTP2Connector { - private let config: HTTP2ClientTransport.Posix.Config - private let eventLoopGroup: any EventLoopGroup - - #if canImport(NIOSSL) - private let nioSSLContext: NIOSSLContext? - private let serverHostname: String? - #endif - - init(eventLoopGroup: any EventLoopGroup, config: HTTP2ClientTransport.Posix.Config) throws { - self.eventLoopGroup = eventLoopGroup - self.config = config - - #if canImport(NIOSSL) - switch self.config.transportSecurity.wrapped { - case .plaintext: - self.nioSSLContext = nil - self.serverHostname = nil - case .tls(let tlsConfig): - do { - self.nioSSLContext = try NIOSSLContext(configuration: TLSConfiguration(tlsConfig)) - self.serverHostname = tlsConfig.serverHostname - } catch { - throw RuntimeError( - code: .transportError, - message: "Couldn't create SSL context, check your TLS configuration.", - cause: error - ) - } - } - #endif - } - - func establishConnection( - to address: GRPCHTTP2Core.SocketAddress - ) async throws -> HTTP2Connection { - let (channel, multiplexer) = try await ClientBootstrap( - group: self.eventLoopGroup - ).connect(to: address) { channel in - channel.eventLoop.makeCompletedFuture { - #if canImport(NIOSSL) - if let nioSSLContext = self.nioSSLContext { - try channel.pipeline.syncOperations.addHandler( - NIOSSLClientHandler( - context: nioSSLContext, - serverHostname: self.serverHostname - ) - ) - } - #endif - - return try channel.pipeline.syncOperations.configureGRPCClientPipeline( - channel: channel, - config: GRPCChannel.Config(posix: self.config) - ) - } - } - - return HTTP2Connection(channel: channel, multiplexer: multiplexer, isPlaintext: true) - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension HTTP2ClientTransport.Posix { - public struct Config: Sendable { - /// Configuration for HTTP/2 connections. - public var http2: HTTP2ClientTransport.Config.HTTP2 - - /// Configuration for backoff used when establishing a connection. - public var backoff: HTTP2ClientTransport.Config.Backoff - - /// Configuration for connection management. - public var connection: HTTP2ClientTransport.Config.Connection - - /// Compression configuration. - public var compression: HTTP2ClientTransport.Config.Compression - - /// The transport's security. - public var transportSecurity: TransportSecurity - - /// Creates a new connection configuration. - /// - /// - Parameters: - /// - http2: HTTP2 configuration. - /// - backoff: Backoff configuration. - /// - connection: Connection configuration. - /// - compression: Compression configuration. - /// - transportSecurity: The transport's security configuration. - /// - /// - SeeAlso: ``defaults(transportSecurity:configure:)`` - public init( - http2: HTTP2ClientTransport.Config.HTTP2, - backoff: HTTP2ClientTransport.Config.Backoff, - connection: HTTP2ClientTransport.Config.Connection, - compression: HTTP2ClientTransport.Config.Compression, - transportSecurity: TransportSecurity - ) { - self.http2 = http2 - self.connection = connection - self.backoff = backoff - self.compression = compression - self.transportSecurity = transportSecurity - } - - /// Default values. - /// - /// - Parameters: - /// - transportSecurity: The security settings applied to the transport. - /// - configure: A closure which allows you to modify the defaults before returning them. - public static func defaults( - transportSecurity: TransportSecurity, - configure: (_ config: inout Self) -> Void = { _ in } - ) -> Self { - var config = Self( - http2: .defaults, - backoff: .defaults, - connection: .defaults, - compression: .defaults, - transportSecurity: transportSecurity - ) - configure(&config) - return config - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension GRPCChannel.Config { - init(posix: HTTP2ClientTransport.Posix.Config) { - self.init( - http2: posix.http2, - backoff: posix.backoff, - connection: posix.connection, - compression: posix.compression - ) - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension ClientTransport where Self == HTTP2ClientTransport.Posix { - /// Creates a new Posix based HTTP/2 client transport. - /// - /// - Parameters: - /// - target: A target to resolve. - /// - config: Configuration for the transport. - /// - resolverRegistry: A registry of resolver factories. - /// - serviceConfig: Service config controlling how the transport should establish and - /// load-balance connections. - /// - eventLoopGroup: The underlying NIO `EventLoopGroup` to run connections on. This must - /// be a `MultiThreadedEventLoopGroup` or an `EventLoop` from - /// a `MultiThreadedEventLoopGroup`. - /// - Throws: When no suitable resolver could be found for the `target`. - public static func http2NIOPosix( - target: any ResolvableTarget, - config: HTTP2ClientTransport.Posix.Config, - resolverRegistry: NameResolverRegistry = .defaults, - serviceConfig: ServiceConfig = ServiceConfig(), - eventLoopGroup: any EventLoopGroup = .singletonMultiThreadedEventLoopGroup - ) throws -> Self { - return try HTTP2ClientTransport.Posix( - target: target, - config: config, - resolverRegistry: resolverRegistry, - serviceConfig: serviceConfig, - eventLoopGroup: eventLoopGroup - ) - } -} diff --git a/Sources/GRPCHTTP2TransportNIOPosix/HTTP2ServerTransport+Posix.swift b/Sources/GRPCHTTP2TransportNIOPosix/HTTP2ServerTransport+Posix.swift deleted file mode 100644 index 48420178c..000000000 --- a/Sources/GRPCHTTP2TransportNIOPosix/HTTP2ServerTransport+Posix.swift +++ /dev/null @@ -1,286 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -public import GRPCCore -public import GRPCHTTP2Core // should be @usableFromInline -internal import NIOCore -internal import NIOExtras -internal import NIOHTTP2 -public import NIOPosix // has to be public because of default argument value in init -private import Synchronization - -#if canImport(NIOSSL) -import NIOSSL -#endif - -extension HTTP2ServerTransport { - /// A `ServerTransport` using HTTP/2 built on top of `NIOPosix`. - /// - /// This transport builds on top of SwiftNIO's Posix networking layer and is suitable for use - /// on Linux and Darwin based platform (macOS, iOS, etc.) However, it's *strongly* recommended - /// that if you are targeting Darwin platforms then you should use the `NIOTS` variant of - /// the `HTTP2ServerTransport`. - /// - /// You can control various aspects of connection creation, management, security and RPC behavior via - /// the ``Config``. - /// - /// Beyond creating the transport you don't need to interact with it directly, instead, pass it - /// to a `GRPCServer`: - /// - /// ```swift - /// try await withThrowingDiscardingTaskGroup { group in - /// let transport = HTTP2ServerTransport.Posix( - /// address: .ipv4(host: "127.0.0.1", port: 0), - /// config: .defaults(transportSecurity: .plaintext) - /// ) - /// let server = GRPCServer(transport: transport, services: someServices) - /// group.addTask { - /// try await server.serve() - /// } - /// - /// // ... - /// } - /// ``` - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public struct Posix: ServerTransport, ListeningServerTransport { - private struct ListenerFactory: HTTP2ListenerFactory { - let config: Config - - func makeListeningChannel( - eventLoopGroup: any EventLoopGroup, - address: GRPCHTTP2Core.SocketAddress, - serverQuiescingHelper: ServerQuiescingHelper - ) async throws -> NIOAsyncChannel { - #if canImport(NIOSSL) - let sslContext: NIOSSLContext? - - switch self.config.transportSecurity.wrapped { - case .plaintext: - sslContext = nil - case .tls(let tlsConfig): - do { - sslContext = try NIOSSLContext(configuration: TLSConfiguration(tlsConfig)) - } catch { - throw RuntimeError( - code: .transportError, - message: "Couldn't create SSL context, check your TLS configuration.", - cause: error - ) - } - } - #endif - - let serverChannel = try await ServerBootstrap(group: eventLoopGroup) - .serverChannelOption(.socketOption(.so_reuseaddr), value: 1) - .serverChannelInitializer { channel in - let quiescingHandler = serverQuiescingHelper.makeServerChannelHandler(channel: channel) - return channel.pipeline.addHandler(quiescingHandler) - } - .bind(to: address) { channel in - channel.eventLoop.makeCompletedFuture { - #if canImport(NIOSSL) - if let sslContext { - try channel.pipeline.syncOperations.addHandler( - NIOSSLServerHandler(context: sslContext) - ) - } - #endif - - let requireALPN: Bool - let scheme: Scheme - switch self.config.transportSecurity.wrapped { - case .plaintext: - requireALPN = false - scheme = .http - case .tls(let tlsConfig): - requireALPN = tlsConfig.requireALPN - scheme = .https - } - - return try channel.pipeline.syncOperations.configureGRPCServerPipeline( - channel: channel, - compressionConfig: self.config.compression, - connectionConfig: self.config.connection, - http2Config: self.config.http2, - rpcConfig: self.config.rpc, - requireALPN: requireALPN, - scheme: scheme - ) - } - } - - return serverChannel - } - } - - private let underlyingTransport: CommonHTTP2ServerTransport - - /// The listening address for this server transport. - /// - /// It is an `async` property because it will only return once the address has been successfully bound. - /// - /// - Throws: A runtime error will be thrown if the address could not be bound or is not bound any - /// longer, because the transport isn't listening anymore. It can also throw if the transport returned an - /// invalid address. - public var listeningAddress: GRPCHTTP2Core.SocketAddress { - get async throws { - try await self.underlyingTransport.listeningAddress - } - } - - /// Create a new `Posix` transport. - /// - /// - Parameters: - /// - address: The address to which the server should be bound. - /// - config: The transport configuration. - /// - eventLoopGroup: The ELG from which to get ELs to run this transport. - public init( - address: GRPCHTTP2Core.SocketAddress, - config: Config, - eventLoopGroup: MultiThreadedEventLoopGroup = .singletonMultiThreadedEventLoopGroup - ) { - let factory = ListenerFactory(config: config) - let helper = ServerQuiescingHelper(group: eventLoopGroup) - self.underlyingTransport = CommonHTTP2ServerTransport( - address: address, - eventLoopGroup: eventLoopGroup, - quiescingHelper: helper, - listenerFactory: factory - ) - } - - public func listen( - streamHandler: @escaping @Sendable ( - _ stream: RPCStream, - _ context: ServerContext - ) async -> Void - ) async throws { - try await self.underlyingTransport.listen(streamHandler: streamHandler) - } - - public func beginGracefulShutdown() { - self.underlyingTransport.beginGracefulShutdown() - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension HTTP2ServerTransport.Posix { - /// Config for the `Posix` transport. - public struct Config: Sendable { - /// Compression configuration. - public var compression: HTTP2ServerTransport.Config.Compression - - /// Connection configuration. - public var connection: HTTP2ServerTransport.Config.Connection - - /// HTTP2 configuration. - public var http2: HTTP2ServerTransport.Config.HTTP2 - - /// RPC configuration. - public var rpc: HTTP2ServerTransport.Config.RPC - - /// The transport's security. - public var transportSecurity: TransportSecurity - - /// Construct a new `Config`. - /// - /// - Parameters: - /// - http2: HTTP2 configuration. - /// - rpc: RPC configuration. - /// - connection: Connection configuration. - /// - compression: Compression configuration. - /// - transportSecurity: The transport's security configuration. - /// - /// - SeeAlso: ``defaults(transportSecurity:configure:)`` - public init( - http2: HTTP2ServerTransport.Config.HTTP2, - rpc: HTTP2ServerTransport.Config.RPC, - connection: HTTP2ServerTransport.Config.Connection, - compression: HTTP2ServerTransport.Config.Compression, - transportSecurity: TransportSecurity - ) { - self.compression = compression - self.connection = connection - self.http2 = http2 - self.rpc = rpc - self.transportSecurity = transportSecurity - } - - /// Default values for the different configurations. - /// - /// - Parameters: - /// - transportSecurity: The security settings applied to the transport. - /// - configure: A closure which allows you to modify the defaults before returning them. - public static func defaults( - transportSecurity: TransportSecurity, - configure: (_ config: inout Self) -> Void = { _ in } - ) -> Self { - var config = Self( - http2: .defaults, - rpc: .defaults, - connection: .defaults, - compression: .defaults, - transportSecurity: transportSecurity - ) - configure(&config) - return config - } - } -} - -extension ServerBootstrap { - @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) - fileprivate func bind( - to address: GRPCHTTP2Core.SocketAddress, - childChannelInitializer: @escaping @Sendable (any Channel) -> EventLoopFuture - ) async throws -> NIOAsyncChannel { - if let virtualSocket = address.virtualSocket { - return try await self.bind( - to: VsockAddress(virtualSocket), - childChannelInitializer: childChannelInitializer - ) - } else { - return try await self.bind( - to: NIOCore.SocketAddress(address), - childChannelInitializer: childChannelInitializer - ) - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension ServerTransport where Self == HTTP2ServerTransport.Posix { - /// Create a new `Posix` based HTTP/2 server transport. - /// - /// - Parameters: - /// - address: The address to which the server should be bound. - /// - config: The transport configuration. - /// - eventLoopGroup: The underlying NIO `EventLoopGroup` to the server on. This must - /// be a `MultiThreadedEventLoopGroup` or an `EventLoop` from - /// a `MultiThreadedEventLoopGroup`. - public static func http2NIOPosix( - address: GRPCHTTP2Core.SocketAddress, - config: HTTP2ServerTransport.Posix.Config, - eventLoopGroup: MultiThreadedEventLoopGroup = .singletonMultiThreadedEventLoopGroup - ) -> Self { - return HTTP2ServerTransport.Posix( - address: address, - config: config, - eventLoopGroup: eventLoopGroup - ) - } -} diff --git a/Sources/GRPCHTTP2TransportNIOPosix/NIOClientBootstrap+SocketAddress.swift b/Sources/GRPCHTTP2TransportNIOPosix/NIOClientBootstrap+SocketAddress.swift deleted file mode 100644 index 5eb6e193e..000000000 --- a/Sources/GRPCHTTP2TransportNIOPosix/NIOClientBootstrap+SocketAddress.swift +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -internal import GRPCCore -internal import GRPCHTTP2Core -internal import NIOCore -internal import NIOPosix - -extension ClientBootstrap { - @available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) - func connect( - to address: GRPCHTTP2Core.SocketAddress, - _ configure: @Sendable @escaping (any Channel) -> EventLoopFuture - ) async throws -> Result { - if let ipv4 = address.ipv4 { - return try await self.connect(to: NIOCore.SocketAddress(ipv4), channelInitializer: configure) - } else if let ipv6 = address.ipv6 { - return try await self.connect(to: NIOCore.SocketAddress(ipv6), channelInitializer: configure) - } else if let uds = address.unixDomainSocket { - return try await self.connect(to: NIOCore.SocketAddress(uds), channelInitializer: configure) - } else if let vsock = address.virtualSocket { - return try await self.connect(to: VsockAddress(vsock), channelInitializer: configure) - } else { - throw RuntimeError( - code: .transportError, - message: """ - Unhandled socket address '\(address)', this is a gRPC Swift bug. Please file an issue \ - against the project. - """ - ) - } - } -} - -extension NIOPosix.VsockAddress { - init(_ address: GRPCHTTP2Core.SocketAddress.VirtualSocket) { - self.init( - cid: ContextID(rawValue: address.contextID.rawValue), - port: Port(rawValue: address.port.rawValue) - ) - } -} diff --git a/Sources/GRPCHTTP2TransportNIOPosix/NIOSSL+GRPC.swift b/Sources/GRPCHTTP2TransportNIOPosix/NIOSSL+GRPC.swift deleted file mode 100644 index 94436f56e..000000000 --- a/Sources/GRPCHTTP2TransportNIOPosix/NIOSSL+GRPC.swift +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ -#if canImport(NIOSSL) -import NIOSSL - -extension NIOSSLSerializationFormats { - fileprivate init(_ format: TLSConfig.SerializationFormat) { - switch format.wrapped { - case .pem: - self = .pem - case .der: - self = .der - } - } -} - -extension Sequence { - func sslCertificateSources() throws -> [NIOSSLCertificateSource] { - var certificateSources: [NIOSSLCertificateSource] = [] - for source in self { - switch source.wrapped { - case .bytes(let bytes, let serializationFormat): - switch serializationFormat.wrapped { - case .der: - certificateSources.append( - .certificate(try NIOSSLCertificate(bytes: bytes, format: .der)) - ) - - case .pem: - let certificates = try NIOSSLCertificate.fromPEMBytes(bytes).map { - NIOSSLCertificateSource.certificate($0) - } - certificateSources.append(contentsOf: certificates) - } - - case .file(let path, let serializationFormat): - switch serializationFormat.wrapped { - case .der: - certificateSources.append( - .certificate(try NIOSSLCertificate(file: path, format: .der)) - ) - - case .pem: - let certificates = try NIOSSLCertificate.fromPEMFile(path).map { - NIOSSLCertificateSource.certificate($0) - } - certificateSources.append(contentsOf: certificates) - } - } - } - return certificateSources - } -} - -extension NIOSSLPrivateKey { - fileprivate convenience init( - privateKey source: TLSConfig.PrivateKeySource - ) throws { - switch source.wrapped { - case .file(let path, let serializationFormat): - try self.init( - file: path, - format: NIOSSLSerializationFormats(serializationFormat) - ) - case .bytes(let bytes, let serializationFormat): - try self.init( - bytes: bytes, - format: NIOSSLSerializationFormats(serializationFormat) - ) - } - } -} - -extension NIOSSLTrustRoots { - fileprivate init(_ trustRoots: TLSConfig.TrustRootsSource) throws { - switch trustRoots.wrapped { - case .certificates(let certificateSources): - let certificates = try certificateSources.map { source in - switch source.wrapped { - case .bytes(let bytes, let serializationFormat): - return try NIOSSLCertificate( - bytes: bytes, - format: NIOSSLSerializationFormats(serializationFormat) - ) - case .file(let path, let serializationFormat): - return try NIOSSLCertificate( - file: path, - format: NIOSSLSerializationFormats(serializationFormat) - ) - } - } - self = .certificates(certificates) - - case .systemDefault: - self = .default - } - } -} - -extension CertificateVerification { - fileprivate init( - _ verificationMode: TLSConfig.CertificateVerification - ) { - switch verificationMode.wrapped { - case .doNotVerify: - self = .none - case .fullVerification: - self = .fullVerification - case .noHostnameVerification: - self = .noHostnameVerification - } - } -} - -extension TLSConfiguration { - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - package init(_ tlsConfig: HTTP2ServerTransport.Posix.Config.TLS) throws { - let certificateChain = try tlsConfig.certificateChain.sslCertificateSources() - let privateKey = try NIOSSLPrivateKey(privateKey: tlsConfig.privateKey) - - self = TLSConfiguration.makeServerConfiguration( - certificateChain: certificateChain, - privateKey: .privateKey(privateKey) - ) - self.minimumTLSVersion = .tlsv12 - self.certificateVerification = CertificateVerification( - tlsConfig.clientCertificateVerification - ) - self.trustRoots = try NIOSSLTrustRoots(tlsConfig.trustRoots) - self.applicationProtocols = ["grpc-exp", "h2"] - } - - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - package init(_ tlsConfig: HTTP2ClientTransport.Posix.Config.TLS) throws { - self = TLSConfiguration.makeClientConfiguration() - self.certificateChain = try tlsConfig.certificateChain.sslCertificateSources() - - if let privateKey = tlsConfig.privateKey { - let privateKeySource = try NIOSSLPrivateKey(privateKey: privateKey) - self.privateKey = .privateKey(privateKeySource) - } - - self.minimumTLSVersion = .tlsv12 - self.certificateVerification = CertificateVerification( - tlsConfig.serverCertificateVerification - ) - self.trustRoots = try NIOSSLTrustRoots(tlsConfig.trustRoots) - self.applicationProtocols = ["grpc-exp", "h2"] - } -} -#endif diff --git a/Sources/GRPCHTTP2TransportNIOPosix/TLSConfig.swift b/Sources/GRPCHTTP2TransportNIOPosix/TLSConfig.swift deleted file mode 100644 index 2e42d58c1..000000000 --- a/Sources/GRPCHTTP2TransportNIOPosix/TLSConfig.swift +++ /dev/null @@ -1,299 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -public enum TLSConfig: Sendable { - /// The serialization format of the provided certificates and private keys. - public struct SerializationFormat: Sendable, Equatable { - package enum Wrapped { - case pem - case der - } - - package let wrapped: Wrapped - - public static let pem = Self(wrapped: .pem) - public static let der = Self(wrapped: .der) - } - - /// A description of where a certificate is coming from: either a byte array or a file. - /// The serialization format is specified by ``TLSConfig/SerializationFormat``. - public struct CertificateSource: Sendable { - package enum Wrapped { - case file(path: String, format: SerializationFormat) - case bytes(bytes: [UInt8], format: SerializationFormat) - } - - package let wrapped: Wrapped - - /// The certificate's source is a file. - /// - Parameters: - /// - path: The file path containing the certificate. - /// - format: The certificate's format, as a ``TLSConfig/SerializationFormat``. - /// - Returns: A source describing the certificate source is the given file. - public static func file(path: String, format: SerializationFormat) -> Self { - Self(wrapped: .file(path: path, format: format)) - } - - /// The certificate's source is an array of bytes. - /// - Parameters: - /// - bytes: The array of bytes making up the certificate. - /// - format: The certificate's format, as a ``TLSConfig/SerializationFormat``. - /// - Returns: A source describing the certificate source is the given bytes. - public static func bytes(_ bytes: [UInt8], format: SerializationFormat) -> Self { - Self(wrapped: .bytes(bytes: bytes, format: format)) - } - } - - /// A description of where the private key is coming from: either a byte array or a file. - /// The serialization format is specified by ``TLSConfig/SerializationFormat``. - public struct PrivateKeySource: Sendable { - package enum Wrapped { - case file(path: String, format: SerializationFormat) - case bytes(bytes: [UInt8], format: SerializationFormat) - } - - package let wrapped: Wrapped - - /// The private key's source is a file. - /// - Parameters: - /// - path: The file path containing the private key. - /// - format: The private key's format, as a ``TLSConfig/SerializationFormat``. - /// - Returns: A source describing the private key source is the given file. - public static func file(path: String, format: SerializationFormat) -> Self { - Self(wrapped: .file(path: path, format: format)) - } - - /// The private key's source is an array of bytes. - /// - Parameters: - /// - bytes: The array of bytes making up the private key. - /// - format: The private key's format, as a ``TLSConfig/SerializationFormat``. - /// - Returns: A source describing the private key source is the given bytes. - public static func bytes( - _ bytes: [UInt8], - format: SerializationFormat - ) -> Self { - Self(wrapped: .bytes(bytes: bytes, format: format)) - } - } - - /// A description of where the trust roots are coming from: either a custom certificate chain, or the system default trust store. - public struct TrustRootsSource: Sendable { - package enum Wrapped { - case certificates([CertificateSource]) - case systemDefault - } - - package let wrapped: Wrapped - - /// A list of ``TLSConfig/CertificateSource``s making up the - /// chain of trust. - /// - Parameter certificateSources: The sources for the certificates that make up the chain of trust. - /// - Returns: A trust root for the given chain of trust. - public static func certificates( - _ certificateSources: [CertificateSource] - ) -> Self { - Self(wrapped: .certificates(certificateSources)) - } - - /// The system default trust store. - public static let systemDefault: Self = Self(wrapped: .systemDefault) - } - - /// How to verify client certificates. - public struct CertificateVerification: Sendable { - package enum Wrapped { - case doNotVerify - case fullVerification - case noHostnameVerification - } - - package let wrapped: Wrapped - - /// All certificate verification disabled. - public static let noVerification: Self = Self(wrapped: .doNotVerify) - - /// Certificates will be validated against the trust store, but will not be checked to see if they are valid for the given hostname. - public static let noHostnameVerification: Self = Self(wrapped: .noHostnameVerification) - - /// Certificates will be validated against the trust store and checked against the hostname of the service we are contacting. - public static let fullVerification: Self = Self(wrapped: .fullVerification) - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension HTTP2ServerTransport.Posix.Config { - /// The security configuration for this connection. - public struct TransportSecurity: Sendable { - package enum Wrapped: Sendable { - case plaintext - case tls(TLS) - } - - package let wrapped: Wrapped - - /// This connection is plaintext: no encryption will take place. - public static let plaintext = Self(wrapped: .plaintext) - - #if canImport(NIOSSL) - /// This connection will use TLS. - public static func tls(_ tls: TLS) -> Self { - Self(wrapped: .tls(tls)) - } - #endif - } - - public struct TLS: Sendable { - /// The certificates the server will offer during negotiation. - public var certificateChain: [TLSConfig.CertificateSource] - - /// The private key associated with the leaf certificate. - public var privateKey: TLSConfig.PrivateKeySource - - /// How to verify the client certificate, if one is presented. - public var clientCertificateVerification: TLSConfig.CertificateVerification - - /// The trust roots to be used when verifying client certificates. - public var trustRoots: TLSConfig.TrustRootsSource - - /// Whether ALPN is required. - /// - /// If this is set to `true` but the client does not support ALPN, then the connection will be rejected. - public var requireALPN: Bool - - /// Create a new HTTP2 NIO Posix transport TLS config, with some values defaulted: - /// - `clientCertificateVerificationMode` equals `doNotVerify` - /// - `trustRoots` equals `systemDefault` - /// - `requireALPN` equals `false` - /// - /// - Parameters: - /// - certificateChain: The certificates the server will offer during negotiation. - /// - privateKey: The private key associated with the leaf certificate. - /// - Returns: A new HTTP2 NIO Posix transport TLS config. - public static func defaults( - certificateChain: [TLSConfig.CertificateSource], - privateKey: TLSConfig.PrivateKeySource - ) -> Self { - Self( - certificateChain: certificateChain, - privateKey: privateKey, - clientCertificateVerification: .noVerification, - trustRoots: .systemDefault, - requireALPN: false - ) - } - - /// Create a new HTTP2 NIO Posix transport TLS config, with some values defaulted to match - /// the requirements of mTLS: - /// - `clientCertificateVerificationMode` equals `noHostnameVerification` - /// - `trustRoots` equals `systemDefault` - /// - `requireALPN` equals `false` - /// - /// - Parameters: - /// - certificateChain: The certificates the server will offer during negotiation. - /// - privateKey: The private key associated with the leaf certificate. - /// - Returns: A new HTTP2 NIO Posix transport TLS config. - public static func mTLS( - certificateChain: [TLSConfig.CertificateSource], - privateKey: TLSConfig.PrivateKeySource - ) -> Self { - Self( - certificateChain: certificateChain, - privateKey: privateKey, - clientCertificateVerification: .noHostnameVerification, - trustRoots: .systemDefault, - requireALPN: false - ) - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension HTTP2ClientTransport.Posix.Config { - /// The security configuration for this connection. - public struct TransportSecurity: Sendable { - package enum Wrapped: Sendable { - case plaintext - case tls(TLS) - } - - package let wrapped: Wrapped - - /// This connection is plaintext: no encryption will take place. - public static let plaintext = Self(wrapped: .plaintext) - - #if canImport(NIOSSL) - /// This connection will use TLS. - public static func tls(_ tls: TLS) -> Self { - Self(wrapped: .tls(tls)) - } - #endif - } - - public struct TLS: Sendable { - /// The certificates the client will offer during negotiation. - public var certificateChain: [TLSConfig.CertificateSource] - - /// The private key associated with the leaf certificate. - public var privateKey: TLSConfig.PrivateKeySource? - - /// How to verify the server certificate, if one is presented. - public var serverCertificateVerification: TLSConfig.CertificateVerification - - /// The trust roots to be used when verifying server certificates. - public var trustRoots: TLSConfig.TrustRootsSource - - /// An optional server hostname to use when verifying certificates. - public var serverHostname: String? - - /// Create a new HTTP2 NIO Posix transport TLS config, with some values defaulted: - /// - `certificateChain` equals `[]` - /// - `privateKey` equals `nil` - /// - `serverCertificateVerification` equals `fullVerification` - /// - `trustRoots` equals `systemDefault` - /// - `serverHostname` equals `nil` - /// - /// - Returns: A new HTTP2 NIO Posix transport TLS config. - public static var defaults: Self { - Self( - certificateChain: [], - privateKey: nil, - serverCertificateVerification: .fullVerification, - trustRoots: .systemDefault, - serverHostname: nil - ) - } - - /// Create a new HTTP2 NIO Posix transport TLS config, with some values defaulted to match - /// the requirements of mTLS: - /// - `trustRoots` equals `systemDefault` - /// - /// - Parameters: - /// - certificateChain: The certificates the client will offer during negotiation. - /// - privateKey: The private key associated with the leaf certificate. - /// - Returns: A new HTTP2 NIO Posix transport TLS config. - public static func mTLS( - certificateChain: [TLSConfig.CertificateSource], - privateKey: TLSConfig.PrivateKeySource - ) -> Self { - Self( - certificateChain: certificateChain, - privateKey: privateKey, - serverCertificateVerification: .fullVerification, - trustRoots: .systemDefault - ) - } - } -} diff --git a/Sources/GRPCHTTP2TransportNIOTransportServices/Exports.swift b/Sources/GRPCHTTP2TransportNIOTransportServices/Exports.swift deleted file mode 100644 index 395308d46..000000000 --- a/Sources/GRPCHTTP2TransportNIOTransportServices/Exports.swift +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -@_exported import GRPCCore -@_exported import GRPCHTTP2Core diff --git a/Sources/GRPCHTTP2TransportNIOTransportServices/HTTP2ClientTransport+TransportServices.swift b/Sources/GRPCHTTP2TransportNIOTransportServices/HTTP2ClientTransport+TransportServices.swift deleted file mode 100644 index ee86e5eb5..000000000 --- a/Sources/GRPCHTTP2TransportNIOTransportServices/HTTP2ClientTransport+TransportServices.swift +++ /dev/null @@ -1,339 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -#if canImport(Network) -public import GRPCCore -public import GRPCHTTP2Core -public import NIOTransportServices // has to be public because of default argument value in init -public import NIOCore // has to be public because of EventLoopGroup param in init - -private import Network - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension HTTP2ClientTransport { - /// A `ClientTransport` using HTTP/2 built on top of `NIOTransportServices`. - /// - /// This transport builds on top of SwiftNIO's Transport Services networking layer and is the recommended - /// variant for use on Darwin-based platforms (macOS, iOS, etc.). - /// If you are targeting Linux platforms then you should use the `NIOPosix` variant of - /// the `HTTP2ClientTransport`. - /// - /// To use this transport you need to provide a 'target' to connect to which will be resolved - /// by an appropriate resolver from the resolver registry. By default the resolver registry can - /// resolve DNS targets, IPv4 and IPv6 targets, and Unix domain socket targets. Virtual Socket - /// targets are not supported with this transport. If you use a custom target you must also provide an - /// appropriately configured registry. - /// - /// You can control various aspects of connection creation, management, security and RPC behavior via - /// the ``Config``. Load balancing policies and other RPC specific behavior can be configured via - /// the `ServiceConfig` (if it isn't provided by a resolver). - /// - /// Beyond creating the transport you don't need to interact with it directly, instead, pass it - /// to a `GRPCClient`: - /// - /// ```swift - /// try await withThrowingDiscardingTaskGroup { group in - /// let transport = try HTTP2ClientTransport.TransportServices( - /// target: .ipv4(host: "example.com"), - /// config: .defaults(transportSecurity: .plaintext) - /// ) - /// let client = GRPCClient(transport: transport) - /// group.addTask { - /// try await client.run() - /// } - /// - /// // ... - /// } - /// ``` - public struct TransportServices: ClientTransport { - private let channel: GRPCChannel - - public var retryThrottle: RetryThrottle? { - self.channel.retryThrottle - } - - /// Creates a new NIOTransportServices-based HTTP/2 client transport. - /// - /// - Parameters: - /// - target: A target to resolve. - /// - config: Configuration for the transport. - /// - resolverRegistry: A registry of resolver factories. - /// - serviceConfig: Service config controlling how the transport should establish and - /// load-balance connections. - /// - eventLoopGroup: The underlying NIO `EventLoopGroup` to run connections on. This must - /// be a `MultiThreadedEventLoopGroup` or an `EventLoop` from - /// a `MultiThreadedEventLoopGroup`. - /// - Throws: When no suitable resolver could be found for the `target`. - public init( - target: any ResolvableTarget, - config: Config, - resolverRegistry: NameResolverRegistry = .defaults, - serviceConfig: ServiceConfig = ServiceConfig(), - eventLoopGroup: any EventLoopGroup = .singletonNIOTSEventLoopGroup - ) throws { - guard let resolver = resolverRegistry.makeResolver(for: target) else { - throw RuntimeError( - code: .transportError, - message: """ - No suitable resolvers to resolve '\(target)'. You must make sure that the resolver \ - registry has a suitable name resolver factory registered for the given target. - """ - ) - } - - self.channel = GRPCChannel( - resolver: resolver, - connector: Connector(eventLoopGroup: eventLoopGroup, config: config), - config: GRPCChannel.Config(transportServices: config), - defaultServiceConfig: serviceConfig - ) - } - - public func connect() async throws { - await self.channel.connect() - } - - public func beginGracefulShutdown() { - self.channel.beginGracefulShutdown() - } - - public func withStream( - descriptor: MethodDescriptor, - options: CallOptions, - _ closure: (RPCStream) async throws -> T - ) async throws -> T { - try await self.channel.withStream(descriptor: descriptor, options: options, closure) - } - - public func config(forMethod descriptor: MethodDescriptor) -> MethodConfig? { - self.channel.config(forMethod: descriptor) - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension HTTP2ClientTransport.TransportServices { - struct Connector: HTTP2Connector { - private let config: HTTP2ClientTransport.TransportServices.Config - private let eventLoopGroup: any EventLoopGroup - - init( - eventLoopGroup: any EventLoopGroup, - config: HTTP2ClientTransport.TransportServices.Config - ) { - self.eventLoopGroup = eventLoopGroup - self.config = config - } - - func establishConnection( - to address: GRPCHTTP2Core.SocketAddress - ) async throws -> HTTP2Connection { - let bootstrap: NIOTSConnectionBootstrap - let isPlainText: Bool - switch self.config.transportSecurity.wrapped { - case .plaintext: - isPlainText = true - bootstrap = NIOTSConnectionBootstrap(group: self.eventLoopGroup) - - case .tls(let tlsConfig): - isPlainText = false - bootstrap = NIOTSConnectionBootstrap(group: self.eventLoopGroup) - .tlsOptions(try NWProtocolTLS.Options(tlsConfig)) - } - - let (channel, multiplexer) = try await bootstrap.connect(to: address) { channel in - channel.eventLoop.makeCompletedFuture { - try channel.pipeline.syncOperations.configureGRPCClientPipeline( - channel: channel, - config: GRPCChannel.Config(transportServices: self.config) - ) - } - } - - return HTTP2Connection( - channel: channel, - multiplexer: multiplexer, - isPlaintext: isPlainText - ) - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension HTTP2ClientTransport.TransportServices { - /// Configuration for the `TransportServices` transport. - public struct Config: Sendable { - /// Configuration for HTTP/2 connections. - public var http2: HTTP2ClientTransport.Config.HTTP2 - - /// Configuration for backoff used when establishing a connection. - public var backoff: HTTP2ClientTransport.Config.Backoff - - /// Configuration for connection management. - public var connection: HTTP2ClientTransport.Config.Connection - - /// Compression configuration. - public var compression: HTTP2ClientTransport.Config.Compression - - /// The transport's security. - public var transportSecurity: TransportSecurity - - /// Creates a new connection configuration. - /// - /// - Parameters: - /// - http2: HTTP2 configuration. - /// - backoff: Backoff configuration. - /// - connection: Connection configuration. - /// - compression: Compression configuration. - /// - transportSecurity: The transport's security configuration. - /// - /// - SeeAlso: ``defaults(transportSecurity:configure:)`` - public init( - http2: HTTP2ClientTransport.Config.HTTP2, - backoff: HTTP2ClientTransport.Config.Backoff, - connection: HTTP2ClientTransport.Config.Connection, - compression: HTTP2ClientTransport.Config.Compression, - transportSecurity: TransportSecurity - ) { - self.http2 = http2 - self.connection = connection - self.backoff = backoff - self.compression = compression - self.transportSecurity = transportSecurity - } - - /// Default values. - /// - /// - Parameters: - /// - transportSecurity: The security settings applied to the transport. - /// - configure: A closure which allows you to modify the defaults before returning them. - public static func defaults( - transportSecurity: TransportSecurity, - configure: (_ config: inout Self) -> Void = { _ in } - ) -> Self { - var config = Self( - http2: .defaults, - backoff: .defaults, - connection: .defaults, - compression: .defaults, - transportSecurity: transportSecurity - ) - configure(&config) - return config - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension GRPCChannel.Config { - init(transportServices config: HTTP2ClientTransport.TransportServices.Config) { - self.init( - http2: config.http2, - backoff: config.backoff, - connection: config.connection, - compression: config.compression - ) - } -} - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -extension NIOTSConnectionBootstrap { - fileprivate func connect( - to address: GRPCHTTP2Core.SocketAddress, - childChannelInitializer: @escaping @Sendable (any Channel) -> EventLoopFuture - ) async throws -> Output { - if address.virtualSocket != nil { - throw RuntimeError( - code: .transportError, - message: """ - Virtual sockets are not supported by 'HTTP2ClientTransport.TransportServices'. \ - Please use the 'HTTP2ClientTransport.Posix' transport. - """ - ) - } else { - return try await self.connect( - to: NIOCore.SocketAddress(address), - channelInitializer: childChannelInitializer - ) - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension ClientTransport where Self == HTTP2ClientTransport.TransportServices { - /// Create a new `TransportServices` based HTTP/2 client transport. - /// - /// - Parameters: - /// - target: A target to resolve. - /// - config: Configuration for the transport. - /// - resolverRegistry: A registry of resolver factories. - /// - serviceConfig: Service config controlling how the transport should establish and - /// load-balance connections. - /// - eventLoopGroup: The underlying NIO `EventLoopGroup` to run connections on. This must - /// be a `NIOTSEventLoopGroup` or an `EventLoop` from - /// a `NIOTSEventLoopGroup`. - /// - Throws: When no suitable resolver could be found for the `target`. - public static func http2NIOTS( - target: any ResolvableTarget, - config: HTTP2ClientTransport.TransportServices.Config, - resolverRegistry: NameResolverRegistry = .defaults, - serviceConfig: ServiceConfig = ServiceConfig(), - eventLoopGroup: any EventLoopGroup = .singletonNIOTSEventLoopGroup - ) throws -> Self { - try HTTP2ClientTransport.TransportServices( - target: target, - config: config, - resolverRegistry: resolverRegistry, - serviceConfig: serviceConfig, - eventLoopGroup: eventLoopGroup - ) - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension NWProtocolTLS.Options { - convenience init(_ tlsConfig: HTTP2ClientTransport.TransportServices.Config.TLS) throws { - self.init() - - guard let sec_identity = sec_identity_create(try tlsConfig.identityProvider()) else { - throw RuntimeError( - code: .transportError, - message: """ - There was an issue creating the SecIdentity required to set up TLS. \ - Please check your TLS configuration. - """ - ) - } - - sec_protocol_options_set_local_identity( - self.securityProtocolOptions, - sec_identity - ) - - sec_protocol_options_set_min_tls_protocol_version( - self.securityProtocolOptions, - .TLSv12 - ) - - for `protocol` in ["grpc-exp", "h2"] { - sec_protocol_options_add_tls_application_protocol( - self.securityProtocolOptions, - `protocol` - ) - } - } -} -#endif diff --git a/Sources/GRPCHTTP2TransportNIOTransportServices/HTTP2ServerTransport+TransportServices.swift b/Sources/GRPCHTTP2TransportNIOTransportServices/HTTP2ServerTransport+TransportServices.swift deleted file mode 100644 index 31fd3a312..000000000 --- a/Sources/GRPCHTTP2TransportNIOTransportServices/HTTP2ServerTransport+TransportServices.swift +++ /dev/null @@ -1,274 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -#if canImport(Network) -public import GRPCCore -public import NIOTransportServices // has to be public because of default argument value in init -public import GRPCHTTP2Core - -private import NIOCore -private import NIOExtras -private import NIOHTTP2 -private import Network - -private import Synchronization - -extension HTTP2ServerTransport { - /// A NIO Transport Services-backed implementation of a server transport. - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public struct TransportServices: ServerTransport, ListeningServerTransport { - private struct ListenerFactory: HTTP2ListenerFactory { - let config: Config - - func makeListeningChannel( - eventLoopGroup: any EventLoopGroup, - address: GRPCHTTP2Core.SocketAddress, - serverQuiescingHelper: ServerQuiescingHelper - ) async throws -> NIOAsyncChannel { - let bootstrap: NIOTSListenerBootstrap - - let requireALPN: Bool - let scheme: Scheme - switch self.config.transportSecurity.wrapped { - case .plaintext: - requireALPN = false - scheme = .http - bootstrap = NIOTSListenerBootstrap(group: eventLoopGroup) - - case .tls(let tlsConfig): - requireALPN = tlsConfig.requireALPN - scheme = .https - bootstrap = NIOTSListenerBootstrap(group: eventLoopGroup) - .tlsOptions(try NWProtocolTLS.Options(tlsConfig)) - } - - let serverChannel = - try await bootstrap - .serverChannelOption(.socketOption(.so_reuseaddr), value: 1) - .serverChannelInitializer { channel in - let quiescingHandler = serverQuiescingHelper.makeServerChannelHandler(channel: channel) - return channel.pipeline.addHandler(quiescingHandler) - } - .bind(to: address) { channel in - channel.eventLoop.makeCompletedFuture { - return try channel.pipeline.syncOperations.configureGRPCServerPipeline( - channel: channel, - compressionConfig: self.config.compression, - connectionConfig: self.config.connection, - http2Config: self.config.http2, - rpcConfig: self.config.rpc, - requireALPN: requireALPN, - scheme: scheme - ) - } - } - - return serverChannel - } - } - - private let underlyingTransport: CommonHTTP2ServerTransport - - /// The listening address for this server transport. - /// - /// It is an `async` property because it will only return once the address has been successfully bound. - /// - /// - Throws: A runtime error will be thrown if the address could not be bound or is not bound any - /// longer, because the transport isn't listening anymore. It can also throw if the transport returned an - /// invalid address. - public var listeningAddress: GRPCHTTP2Core.SocketAddress { - get async throws { - try await self.underlyingTransport.listeningAddress - } - } - - /// Create a new `TransportServices` transport. - /// - /// - Parameters: - /// - address: The address to which the server should be bound. - /// - config: The transport configuration. - /// - eventLoopGroup: The ELG from which to get ELs to run this transport. - public init( - address: GRPCHTTP2Core.SocketAddress, - config: Config, - eventLoopGroup: NIOTSEventLoopGroup = .singletonNIOTSEventLoopGroup - ) { - let factory = ListenerFactory(config: config) - let helper = ServerQuiescingHelper(group: eventLoopGroup) - self.underlyingTransport = CommonHTTP2ServerTransport( - address: address, - eventLoopGroup: eventLoopGroup, - quiescingHelper: helper, - listenerFactory: factory - ) - } - - public func listen( - streamHandler: @escaping @Sendable ( - _ stream: RPCStream, - _ context: ServerContext - ) async -> Void - ) async throws { - try await self.underlyingTransport.listen(streamHandler: streamHandler) - } - - public func beginGracefulShutdown() { - self.underlyingTransport.beginGracefulShutdown() - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension HTTP2ServerTransport.TransportServices { - /// Configuration for the `TransportServices` transport. - public struct Config: Sendable { - /// Compression configuration. - public var compression: HTTP2ServerTransport.Config.Compression - - /// Connection configuration. - public var connection: HTTP2ServerTransport.Config.Connection - - /// HTTP2 configuration. - public var http2: HTTP2ServerTransport.Config.HTTP2 - - /// RPC configuration. - public var rpc: HTTP2ServerTransport.Config.RPC - - /// The transport's security. - public var transportSecurity: TransportSecurity - - /// Construct a new `Config`. - /// - Parameters: - /// - compression: Compression configuration. - /// - connection: Connection configuration. - /// - http2: HTTP2 configuration. - /// - rpc: RPC configuration. - /// - transportSecurity: The transport's security configuration. - public init( - compression: HTTP2ServerTransport.Config.Compression, - connection: HTTP2ServerTransport.Config.Connection, - http2: HTTP2ServerTransport.Config.HTTP2, - rpc: HTTP2ServerTransport.Config.RPC, - transportSecurity: TransportSecurity - ) { - self.compression = compression - self.connection = connection - self.http2 = http2 - self.rpc = rpc - self.transportSecurity = transportSecurity - } - - /// Default values for the different configurations. - /// - /// - Parameters: - /// - transportSecurity: The transport's security configuration. - /// - configure: A closure which allows you to modify the defaults before returning them. - public static func defaults( - transportSecurity: TransportSecurity, - configure: (_ config: inout Self) -> Void = { _ in } - ) -> Self { - var config = Self( - compression: .defaults, - connection: .defaults, - http2: .defaults, - rpc: .defaults, - transportSecurity: transportSecurity - ) - configure(&config) - return config - } - } -} - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -extension NIOTSListenerBootstrap { - fileprivate func bind( - to address: GRPCHTTP2Core.SocketAddress, - childChannelInitializer: @escaping @Sendable (any Channel) -> EventLoopFuture - ) async throws -> NIOAsyncChannel { - if address.virtualSocket != nil { - throw RuntimeError( - code: .transportError, - message: """ - Virtual sockets are not supported by 'HTTP2ServerTransport.TransportServices'. \ - Please use the 'HTTP2ServerTransport.Posix' transport. - """ - ) - } else { - return try await self.bind( - to: NIOCore.SocketAddress(address), - childChannelInitializer: childChannelInitializer - ) - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension ServerTransport where Self == HTTP2ServerTransport.TransportServices { - /// Create a new `TransportServices` based HTTP/2 server transport. - /// - /// - Parameters: - /// - address: The address to which the server should be bound. - /// - config: The transport configuration. - /// - eventLoopGroup: The underlying NIO `EventLoopGroup` to the server on. This must - /// be a `NIOTSEventLoopGroup` or an `EventLoop` from a `NIOTSEventLoopGroup`. - public static func http2NIOTS( - address: GRPCHTTP2Core.SocketAddress, - config: HTTP2ServerTransport.TransportServices.Config, - eventLoopGroup: NIOTSEventLoopGroup = .singletonNIOTSEventLoopGroup - ) -> Self { - return HTTP2ServerTransport.TransportServices( - address: address, - config: config, - eventLoopGroup: eventLoopGroup - ) - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension NWProtocolTLS.Options { - convenience init(_ tlsConfig: HTTP2ServerTransport.TransportServices.Config.TLS) throws { - self.init() - - guard let sec_identity = sec_identity_create(try tlsConfig.identityProvider()) else { - throw RuntimeError( - code: .transportError, - message: """ - There was an issue creating the SecIdentity required to set up TLS. \ - Please check your TLS configuration. - """ - ) - } - - sec_protocol_options_set_local_identity( - self.securityProtocolOptions, - sec_identity - ) - - sec_protocol_options_set_min_tls_protocol_version( - self.securityProtocolOptions, - .TLSv12 - ) - - for `protocol` in ["grpc-exp", "h2"] { - sec_protocol_options_add_tls_application_protocol( - self.securityProtocolOptions, - `protocol` - ) - } - } -} -#endif diff --git a/Sources/GRPCHTTP2TransportNIOTransportServices/TLSConfig.swift b/Sources/GRPCHTTP2TransportNIOTransportServices/TLSConfig.swift deleted file mode 100644 index 94bc7dcb2..000000000 --- a/Sources/GRPCHTTP2TransportNIOTransportServices/TLSConfig.swift +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -#if canImport(Network) -public import Network - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension HTTP2ServerTransport.TransportServices.Config { - /// The security configuration for this connection. - public struct TransportSecurity: Sendable { - package enum Wrapped: Sendable { - case plaintext - case tls(TLS) - } - - package let wrapped: Wrapped - - /// This connection is plaintext: no encryption will take place. - public static let plaintext = Self(wrapped: .plaintext) - - /// This connection will use TLS. - public static func tls(_ tls: TLS) -> Self { - Self(wrapped: .tls(tls)) - } - } - - public struct TLS: Sendable { - /// A provider for the `SecIdentity` to be used when setting up TLS. - public var identityProvider: @Sendable () throws -> SecIdentity - - /// Whether ALPN is required. - /// - /// If this is set to `true` but the client does not support ALPN, then the connection will be rejected. - public var requireALPN: Bool - - /// Create a new HTTP2 NIO Transport Services transport TLS config, with some values defaulted: - /// - `requireALPN` equals `false` - /// - /// - Returns: A new HTTP2 NIO Transport Services transport TLS config. - public static func defaults( - identityProvider: @Sendable @escaping () throws -> SecIdentity - ) -> Self { - Self( - identityProvider: identityProvider, - requireALPN: false - ) - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension HTTP2ClientTransport.TransportServices.Config { - /// The security configuration for this connection. - public struct TransportSecurity: Sendable { - package enum Wrapped: Sendable { - case plaintext - case tls(TLS) - } - - package let wrapped: Wrapped - - /// This connection is plaintext: no encryption will take place. - public static let plaintext = Self(wrapped: .plaintext) - - /// This connection will use TLS. - public static func tls(_ tls: TLS) -> Self { - Self(wrapped: .tls(tls)) - } - } - - public struct TLS: Sendable { - /// A provider for the `SecIdentity` to be used when setting up TLS. - public var identityProvider: @Sendable () throws -> SecIdentity - - /// Create a new HTTP2 NIO Transport Services transport TLS config. - public init(identityProvider: @Sendable @escaping () throws -> SecIdentity) { - self.identityProvider = identityProvider - } - } -} -#endif diff --git a/Sources/GRPCInProcessTransport/Exports.swift b/Sources/GRPCInProcessTransport/Exports.swift deleted file mode 100644 index 1f32ac4d1..000000000 --- a/Sources/GRPCInProcessTransport/Exports.swift +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -@_exported import GRPCCore diff --git a/Sources/GRPCInProcessTransport/InProcessClientTransport.swift b/Sources/GRPCInProcessTransport/InProcessClientTransport.swift deleted file mode 100644 index 822138f33..000000000 --- a/Sources/GRPCInProcessTransport/InProcessClientTransport.swift +++ /dev/null @@ -1,353 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -public import GRPCCore -private import Synchronization - -/// An in-process implementation of a ``ClientTransport``. -/// -/// This is useful when you're interested in testing your application without any actual networking layers -/// involved, as the client and server will communicate directly with each other via in-process streams. -/// -/// To use this client, you'll have to provide an ``InProcessServerTransport`` upon creation, as well -/// as a ``ServiceConfig``. -/// -/// Once you have a client, you must keep a long-running task executing ``connect()``, which -/// will return only once all streams have been finished and ``beginGracefulShutdown()`` has been called on this client; or -/// when the containing task is cancelled. -/// -/// To execute requests using this client, use ``withStream(descriptor:options:_:)``. If this function is -/// called before ``connect()`` is called, then any streams will remain pending and the call will -/// block until ``connect()`` is called or the task is cancelled. -/// -/// - SeeAlso: ``ClientTransport`` -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -public final class InProcessClientTransport: ClientTransport { - private enum State: Sendable { - struct UnconnectedState { - var serverTransport: InProcessServerTransport - var pendingStreams: [AsyncStream.Continuation] - - init(serverTransport: InProcessServerTransport) { - self.serverTransport = serverTransport - self.pendingStreams = [] - } - } - - struct ConnectedState { - var serverTransport: InProcessServerTransport - var nextStreamID: Int - var openStreams: - [Int: ( - RPCStream, - RPCStream< - RPCAsyncSequence, RPCWriter.Closable - > - )] - var signalEndContinuation: AsyncStream.Continuation - - init( - fromUnconnected state: UnconnectedState, - signalEndContinuation: AsyncStream.Continuation - ) { - self.serverTransport = state.serverTransport - self.nextStreamID = 0 - self.openStreams = [:] - self.signalEndContinuation = signalEndContinuation - } - } - - struct ClosedState { - var openStreams: - [Int: ( - RPCStream, - RPCStream< - RPCAsyncSequence, RPCWriter.Closable - > - )] - var signalEndContinuation: AsyncStream.Continuation? - - init() { - self.openStreams = [:] - self.signalEndContinuation = nil - } - - init(fromConnected state: ConnectedState) { - self.openStreams = state.openStreams - self.signalEndContinuation = state.signalEndContinuation - } - } - - case unconnected(UnconnectedState) - case connected(ConnectedState) - case closed(ClosedState) - } - - public typealias Inbound = RPCAsyncSequence - public typealias Outbound = RPCWriter.Closable - - public let retryThrottle: RetryThrottle? - - private let methodConfig: MethodConfigs - private let state: Mutex - - /// Creates a new in-process client transport. - /// - /// - Parameters: - /// - server: The in-process server transport to connect to. - /// - serviceConfig: Service configuration. - public init( - server: InProcessServerTransport, - serviceConfig: ServiceConfig = ServiceConfig() - ) { - self.retryThrottle = serviceConfig.retryThrottling.map { RetryThrottle(policy: $0) } - self.methodConfig = MethodConfigs(serviceConfig: serviceConfig) - self.state = Mutex(.unconnected(.init(serverTransport: server))) - } - - /// Establish and maintain a connection to the remote destination. - /// - /// Maintains a long-lived connection, or set of connections, to a remote destination. - /// Connections may be added or removed over time as required by the implementation and the - /// demand for streams by the client. - /// - /// Implementations of this function will typically create a long-lived task group which - /// maintains connections. The function exits when all open streams have been closed and new connections - /// are no longer required by the caller who signals this by calling ``beginGracefulShutdown()``, or by cancelling the - /// task this function runs in. - public func connect() async throws { - let (stream, continuation) = AsyncStream.makeStream() - try self.state.withLock { state in - switch state { - case .unconnected(let unconnectedState): - state = .connected( - .init( - fromUnconnected: unconnectedState, - signalEndContinuation: continuation - ) - ) - for pendingStream in unconnectedState.pendingStreams { - pendingStream.finish() - } - case .connected: - throw RPCError( - code: .failedPrecondition, - message: "Already connected to server." - ) - case .closed: - throw RPCError( - code: .failedPrecondition, - message: "Can't connect to server, transport is closed." - ) - } - } - - for await _ in stream { - // This for-await loop will exit (and thus `connect()` will return) - // only when the task is cancelled, or when the stream's continuation is - // finished - whichever happens first. - // The continuation will be finished when `close()` is called and there - // are no more open streams. - } - - // If at this point there are any open streams, it's because Cancellation - // occurred and all open streams must now be closed. - let openStreams = self.state.withLock { state in - switch state { - case .unconnected: - // We have transitioned to connected, and we can't transition back. - fatalError("Invalid state") - case .connected(let connectedState): - state = .closed(.init()) - return connectedState.openStreams.values - case .closed(let closedState): - return closedState.openStreams.values - } - } - - for (clientStream, serverStream) in openStreams { - await clientStream.outbound.finish(throwing: CancellationError()) - await serverStream.outbound.finish(throwing: CancellationError()) - } - } - - /// Signal to the transport that no new streams may be created. - /// - /// Existing streams may run to completion naturally but calling ``withStream(descriptor:options:_:)`` - /// will result in an ``RPCError`` with code ``RPCError/Code/failedPrecondition`` being thrown. - /// - /// If you want to forcefully cancel all active streams then cancel the task running ``connect()``. - public func beginGracefulShutdown() { - let maybeContinuation: AsyncStream.Continuation? = self.state.withLock { state in - switch state { - case .unconnected: - state = .closed(.init()) - return nil - case .connected(let connectedState): - if connectedState.openStreams.count == 0 { - state = .closed(.init()) - return connectedState.signalEndContinuation - } else { - state = .closed(.init(fromConnected: connectedState)) - return nil - } - case .closed: - return nil - } - } - maybeContinuation?.finish() - } - - /// Opens a stream using the transport, and uses it as input into a user-provided closure. - /// - /// - Important: The opened stream is closed after the closure is finished. - /// - /// This transport implementation throws ``RPCError/Code/failedPrecondition`` if the transport - /// is closing or has been closed. - /// - /// This implementation will queue any streams (and thus block this call) if this function is called before - /// ``connect()``, until a connection is established - at which point all streams will be - /// created. - /// - /// - Parameters: - /// - descriptor: A description of the method to open a stream for. - /// - options: Options specific to the stream. - /// - closure: A closure that takes the opened stream as parameter. - /// - Returns: Whatever value was returned from `closure`. - public func withStream( - descriptor: MethodDescriptor, - options: CallOptions, - _ closure: (RPCStream) async throws -> T - ) async throws -> T { - let request = GRPCAsyncThrowingStream.makeStream(of: RPCRequestPart.self) - let response = GRPCAsyncThrowingStream.makeStream(of: RPCResponsePart.self) - - let clientStream = RPCStream( - descriptor: descriptor, - inbound: RPCAsyncSequence(wrapping: response.stream), - outbound: RPCWriter.Closable(wrapping: request.continuation) - ) - - let serverStream = RPCStream( - descriptor: descriptor, - inbound: RPCAsyncSequence(wrapping: request.stream), - outbound: RPCWriter.Closable(wrapping: response.continuation) - ) - - let waitForConnectionStream: AsyncStream? = self.state.withLock { state in - if case .unconnected(var unconnectedState) = state { - let (stream, continuation) = AsyncStream.makeStream() - unconnectedState.pendingStreams.append(continuation) - state = .unconnected(unconnectedState) - return stream - } - return nil - } - - if let waitForConnectionStream { - for await _ in waitForConnectionStream { - // This loop will exit either when the task is cancelled or when the - // client connects and this stream can be opened. - } - try Task.checkCancellation() - } - - let acceptStream: Result = self.state.withLock { state in - switch state { - case .unconnected: - // The state cannot be unconnected because if it was, then the above - // for-await loop on `pendingStream` would have not returned. - // The only other option is for the task to have been cancelled, - // and that's why we check for cancellation right after the loop. - fatalError("Invalid state.") - - case .connected(var connectedState): - let streamID = connectedState.nextStreamID - do { - try connectedState.serverTransport.acceptStream(serverStream) - connectedState.openStreams[streamID] = (clientStream, serverStream) - connectedState.nextStreamID += 1 - state = .connected(connectedState) - return .success(streamID) - } catch let acceptStreamError as RPCError { - return .failure(acceptStreamError) - } catch { - return .failure(RPCError(code: .unknown, message: "Unknown error: \(error).")) - } - - case .closed: - let error = RPCError(code: .failedPrecondition, message: "The client transport is closed.") - return .failure(error) - } - } - - switch acceptStream { - case .success(let streamID): - let streamHandlingResult: Result - do { - let result = try await closure(clientStream) - streamHandlingResult = .success(result) - } catch { - streamHandlingResult = .failure(error) - } - - await clientStream.outbound.finish() - self.removeStream(id: streamID) - - return try streamHandlingResult.get() - - case .failure(let error): - await serverStream.outbound.finish(throwing: error) - await clientStream.outbound.finish(throwing: error) - throw error - } - } - - private func removeStream(id streamID: Int) { - let maybeEndContinuation = self.state.withLock { state in - switch state { - case .unconnected: - // The state cannot be unconnected at this point, because if we made - // it this far, it's because the transport was connected. - // Once connected, it's impossible to transition back to unconnected, - // so this is an invalid state. - fatalError("Invalid state") - case .connected(var connectedState): - connectedState.openStreams.removeValue(forKey: streamID) - state = .connected(connectedState) - case .closed(var closedState): - closedState.openStreams.removeValue(forKey: streamID) - state = .closed(closedState) - if closedState.openStreams.isEmpty { - // This was the last open stream: signal the closure of the client. - return closedState.signalEndContinuation - } - } - return nil - } - maybeEndContinuation?.finish() - } - - /// Returns the execution configuration for a given method. - /// - /// - Parameter descriptor: The method to lookup configuration for. - /// - Returns: Execution configuration for the method, if it exists. - public func config( - forMethod descriptor: MethodDescriptor - ) -> MethodConfig? { - self.methodConfig[descriptor] - } -} diff --git a/Sources/GRPCInProcessTransport/InProcessServerTransport.swift b/Sources/GRPCInProcessTransport/InProcessServerTransport.swift deleted file mode 100644 index 2bb2ed57d..000000000 --- a/Sources/GRPCInProcessTransport/InProcessServerTransport.swift +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -public import GRPCCore - -/// An in-process implementation of a ``ServerTransport``. -/// -/// This is useful when you're interested in testing your application without any actual networking layers -/// involved, as the client and server will communicate directly with each other via in-process streams. -/// -/// To use this server, you call ``listen(_:)`` and iterate over the returned `AsyncSequence` to get all -/// RPC requests made from clients (as ``RPCStream``s). -/// To stop listening to new requests, call ``beginGracefulShutdown()``. -/// -/// - SeeAlso: ``ClientTransport`` -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -public struct InProcessServerTransport: ServerTransport, Sendable { - public typealias Inbound = RPCAsyncSequence - public typealias Outbound = RPCWriter.Closable - - private let newStreams: AsyncStream> - private let newStreamsContinuation: AsyncStream>.Continuation - - /// Creates a new instance of ``InProcessServerTransport``. - public init() { - (self.newStreams, self.newStreamsContinuation) = AsyncStream.makeStream() - } - - /// Publish a new ``RPCStream``, which will be returned by the transport's ``events`` - /// successful case. - /// - /// - Parameter stream: The new ``RPCStream`` to publish. - /// - Throws: ``RPCError`` with code ``RPCError/Code-swift.struct/failedPrecondition`` - /// if the server transport stopped listening to new streams (i.e., if ``beginGracefulShutdown()`` has been called). - internal func acceptStream(_ stream: RPCStream) throws { - let yieldResult = self.newStreamsContinuation.yield(stream) - if case .terminated = yieldResult { - throw RPCError( - code: .failedPrecondition, - message: "The server transport is closed." - ) - } - } - - public func listen( - streamHandler: @escaping @Sendable ( - _ stream: RPCStream, - _ context: ServerContext - ) async -> Void - ) async throws { - await withDiscardingTaskGroup { group in - for await stream in self.newStreams { - group.addTask { - let context = ServerContext(descriptor: stream.descriptor) - await streamHandler(stream, context) - } - } - } - } - - /// Stop listening to any new ``RPCStream`` publications. - /// - /// - SeeAlso: ``ServerTransport`` - public func beginGracefulShutdown() { - self.newStreamsContinuation.finish() - } -} diff --git a/Sources/GRPCInProcessTransport/InProcessTransport.swift b/Sources/GRPCInProcessTransport/InProcessTransport.swift deleted file mode 100644 index 32a2002e9..000000000 --- a/Sources/GRPCInProcessTransport/InProcessTransport.swift +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2023, gRPC Authors All rights reserved. - * - * 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. - */ - -public import GRPCCore - -public enum InProcessTransport { - /// Returns a pair containing an ``InProcessServerTransport`` and an ``InProcessClientTransport``. - /// - /// This function is purely for convenience and does no more than constructing a server transport - /// and a client using that server transport. - /// - /// - Parameters: - /// - serviceConfig: Configuration describing how methods should be executed. - /// - Returns: A tuple containing the connected server and client in-process transports. - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public static func makePair( - serviceConfig: ServiceConfig = ServiceConfig() - ) -> (server: InProcessServerTransport, client: InProcessClientTransport) { - let server = InProcessServerTransport() - let client = InProcessClientTransport( - server: server, - serviceConfig: serviceConfig - ) - return (server, client) - } -} diff --git a/Sources/GRPCInterceptors/ClientTracingInterceptor.swift b/Sources/GRPCInterceptors/ClientTracingInterceptor.swift deleted file mode 100644 index 9da8a1f26..000000000 --- a/Sources/GRPCInterceptors/ClientTracingInterceptor.swift +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -public import GRPCCore -internal import Tracing - -/// A client interceptor that injects tracing information into the request. -/// -/// The tracing information is taken from the current `ServiceContext`, and injected into the request's -/// metadata. It will then be picked up by the server-side ``ServerTracingInterceptor``. -/// -/// For more information, refer to the documentation for `swift-distributed-tracing`. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -public struct ClientTracingInterceptor: ClientInterceptor { - private let injector: ClientRequestInjector - private let emitEventOnEachWrite: Bool - - /// Create a new instance of a ``ClientTracingInterceptor``. - /// - /// - Parameter emitEventOnEachWrite: If `true`, each request part sent and response part - /// received will be recorded as a separate event in a tracing span. Otherwise, only the request/response - /// start and end will be recorded as events. - public init(emitEventOnEachWrite: Bool = false) { - self.injector = ClientRequestInjector() - self.emitEventOnEachWrite = emitEventOnEachWrite - } - - /// This interceptor will inject as the request's metadata whatever `ServiceContext` key-value pairs - /// have been made available by the tracing implementation bootstrapped in your application. - /// - /// Which key-value pairs are injected will depend on the specific tracing implementation - /// that has been configured when bootstrapping `swift-distributed-tracing` in your application. - public func intercept( - request: ClientRequest.Stream, - context: ClientContext, - next: ( - ClientRequest.Stream, - ClientContext - ) async throws -> ClientResponse.Stream - ) async throws -> ClientResponse.Stream where Input: Sendable, Output: Sendable { - var request = request - let tracer = InstrumentationSystem.tracer - let serviceContext = ServiceContext.current ?? .topLevel - - tracer.inject( - serviceContext, - into: &request.metadata, - using: self.injector - ) - - return try await tracer.withSpan( - context.descriptor.fullyQualifiedMethod, - context: serviceContext, - ofKind: .client - ) { span in - span.addEvent("Request started") - - if self.emitEventOnEachWrite { - let wrappedProducer = request.producer - request.producer = { writer in - let eventEmittingWriter = HookedWriter( - wrapping: writer, - beforeEachWrite: { - span.addEvent("Sending request part") - }, - afterEachWrite: { - span.addEvent("Sent request part") - } - ) - - do { - try await wrappedProducer(RPCWriter(wrapping: eventEmittingWriter)) - } catch { - span.addEvent("Error encountered") - throw error - } - - span.addEvent("Request end") - } - } - - var response: ClientResponse.Stream - do { - response = try await next(request, context) - } catch { - span.addEvent("Error encountered") - throw error - } - - switch response.accepted { - case .success(var success): - if self.emitEventOnEachWrite { - let onEachPartRecordingSequence = success.bodyParts.map { element in - span.addEvent("Received response part") - return element - } - let onFinishRecordingSequence = OnFinishAsyncSequence( - wrapping: onEachPartRecordingSequence - ) { - span.addEvent("Received response end") - } - success.bodyParts = RPCAsyncSequence(wrapping: onFinishRecordingSequence) - response.accepted = .success(success) - } else { - let onFinishRecordingSequence = OnFinishAsyncSequence(wrapping: success.bodyParts) { - span.addEvent("Received response end") - } - success.bodyParts = RPCAsyncSequence(wrapping: onFinishRecordingSequence) - response.accepted = .success(success) - } - case .failure: - span.addEvent("Received error response") - } - - return response - } - } -} - -/// An injector responsible for injecting the required instrumentation keys from the `ServiceContext` into -/// the request metadata. -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -struct ClientRequestInjector: Instrumentation.Injector { - typealias Carrier = Metadata - - func inject(_ value: String, forKey key: String, into carrier: inout Carrier) { - carrier.addString(value, forKey: key) - } -} diff --git a/Sources/GRPCInterceptors/HookedWriter.swift b/Sources/GRPCInterceptors/HookedWriter.swift deleted file mode 100644 index 9d85df044..000000000 --- a/Sources/GRPCInterceptors/HookedWriter.swift +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ -internal import GRPCCore -internal import Tracing - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -struct HookedWriter: RPCWriterProtocol { - private let writer: any RPCWriterProtocol - private let beforeEachWrite: @Sendable () -> Void - private let afterEachWrite: @Sendable () -> Void - - init( - wrapping other: some RPCWriterProtocol, - beforeEachWrite: @Sendable @escaping () -> Void, - afterEachWrite: @Sendable @escaping () -> Void - ) { - self.writer = other - self.beforeEachWrite = beforeEachWrite - self.afterEachWrite = afterEachWrite - } - - func write(_ element: Element) async throws { - self.beforeEachWrite() - try await self.writer.write(element) - self.afterEachWrite() - } - - func write(contentsOf elements: some Sequence) async throws { - self.beforeEachWrite() - try await self.writer.write(contentsOf: elements) - self.afterEachWrite() - } -} diff --git a/Sources/GRPCInterceptors/OnFinishAsyncSequence.swift b/Sources/GRPCInterceptors/OnFinishAsyncSequence.swift deleted file mode 100644 index d07a8efec..000000000 --- a/Sources/GRPCInterceptors/OnFinishAsyncSequence.swift +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -struct OnFinishAsyncSequence: AsyncSequence, Sendable { - private let _makeAsyncIterator: @Sendable () -> AsyncIterator - - init( - wrapping other: S, - onFinish: @escaping @Sendable () -> Void - ) where S.Element == Element, S: Sendable { - self._makeAsyncIterator = { - AsyncIterator(wrapping: other.makeAsyncIterator(), onFinish: onFinish) - } - } - - func makeAsyncIterator() -> AsyncIterator { - self._makeAsyncIterator() - } - - struct AsyncIterator: AsyncIteratorProtocol { - private var iterator: any AsyncIteratorProtocol - private var onFinish: (@Sendable () -> Void)? - - fileprivate init( - wrapping other: Iterator, - onFinish: @escaping @Sendable () -> Void - ) where Iterator: AsyncIteratorProtocol, Iterator.Element == Element { - self.iterator = other - self.onFinish = onFinish - } - - mutating func next() async throws -> Element? { - let elem = try await self.iterator.next() - - if elem == nil { - self.onFinish?() - self.onFinish = nil - } - - return elem as? Element - } - } -} diff --git a/Sources/GRPCInterceptors/ServerTracingInterceptor.swift b/Sources/GRPCInterceptors/ServerTracingInterceptor.swift deleted file mode 100644 index a2ebe456c..000000000 --- a/Sources/GRPCInterceptors/ServerTracingInterceptor.swift +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -public import GRPCCore -internal import Tracing - -/// A server interceptor that extracts tracing information from the request. -/// -/// The extracted tracing information is made available to user code via the current `ServiceContext`. -/// For more information, refer to the documentation for `swift-distributed-tracing`. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -public struct ServerTracingInterceptor: ServerInterceptor { - private let extractor: ServerRequestExtractor - private let emitEventOnEachWrite: Bool - - /// Create a new instance of a ``ServerTracingInterceptor``. - /// - /// - Parameter emitEventOnEachWrite: If `true`, each response part sent and request part - /// received will be recorded as a separate event in a tracing span. Otherwise, only the request/response - /// start and end will be recorded as events. - public init(emitEventOnEachWrite: Bool = false) { - self.extractor = ServerRequestExtractor() - self.emitEventOnEachWrite = emitEventOnEachWrite - } - - /// This interceptor will extract whatever `ServiceContext` key-value pairs have been inserted into the - /// request's metadata, and will make them available to user code via the `ServiceContext/current` - /// context. - /// - /// Which key-value pairs are extracted and made available will depend on the specific tracing implementation - /// that has been configured when bootstrapping `swift-distributed-tracing` in your application. - public func intercept( - request: ServerRequest.Stream, - context: ServerContext, - next: @Sendable (ServerRequest.Stream, ServerContext) async throws -> - ServerResponse.Stream - ) async throws -> ServerResponse.Stream where Input: Sendable, Output: Sendable { - var serviceContext = ServiceContext.topLevel - let tracer = InstrumentationSystem.tracer - - tracer.extract( - request.metadata, - into: &serviceContext, - using: self.extractor - ) - - return try await ServiceContext.withValue(serviceContext) { - try await tracer.withSpan( - context.descriptor.fullyQualifiedMethod, - context: serviceContext, - ofKind: .server - ) { span in - span.addEvent("Received request start") - - var request = request - - if self.emitEventOnEachWrite { - request.messages = RPCAsyncSequence( - wrapping: request.messages.map { element in - span.addEvent("Received request part") - return element - } - ) - } - - var response = try await next(request, context) - - span.addEvent("Received request end") - - switch response.accepted { - case .success(var success): - let wrappedProducer = success.producer - - if self.emitEventOnEachWrite { - success.producer = { writer in - let eventEmittingWriter = HookedWriter( - wrapping: writer, - beforeEachWrite: { - span.addEvent("Sending response part") - }, - afterEachWrite: { - span.addEvent("Sent response part") - } - ) - - let wrappedResult: Metadata - do { - wrappedResult = try await wrappedProducer( - RPCWriter(wrapping: eventEmittingWriter) - ) - } catch { - span.addEvent("Error encountered") - throw error - } - - span.addEvent("Sent response end") - return wrappedResult - } - } else { - success.producer = { writer in - let wrappedResult: Metadata - do { - wrappedResult = try await wrappedProducer(writer) - } catch { - span.addEvent("Error encountered") - throw error - } - - span.addEvent("Sent response end") - return wrappedResult - } - } - - response = .init(accepted: .success(success)) - case .failure: - span.addEvent("Sent error response") - } - - return response - } - } - } -} - -/// An extractor responsible for extracting the required instrumentation keys from request metadata. -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -struct ServerRequestExtractor: Instrumentation.Extractor { - typealias Carrier = Metadata - - func extract(key: String, from carrier: Carrier) -> String? { - var values = carrier[stringValues: key].makeIterator() - // There should only be one value for each key. If more, pick just one. - return values.next() - } -} diff --git a/Sources/GRPCProtobuf/Coding.swift b/Sources/GRPCProtobuf/Coding.swift deleted file mode 100644 index df2e10f45..000000000 --- a/Sources/GRPCProtobuf/Coding.swift +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -public import GRPCCore -public import SwiftProtobuf - -/// Serializes a Protobuf message into a sequence of bytes. -public struct ProtobufSerializer: GRPCCore.MessageSerializer { - public init() {} - - /// Serializes a `Message` into a sequence of bytes. - /// - /// - Parameter message: The message to serialize. - /// - Returns: An array of serialized bytes representing the message. - public func serialize(_ message: Message) throws -> [UInt8] { - do { - return try message.serializedBytes() - } catch let error { - throw RPCError( - code: .invalidArgument, - message: "Can't serialize message of type \(type(of: message)).", - cause: error - ) - } - } -} - -/// Deserializes a sequence of bytes into a Protobuf message. -public struct ProtobufDeserializer: GRPCCore.MessageDeserializer { - public init() {} - - /// Deserializes a sequence of bytes into a `Message`. - /// - /// - Parameter serializedMessageBytes: The array of bytes to deserialize. - /// - Returns: The deserialized message. - public func deserialize(_ serializedMessageBytes: [UInt8]) throws -> Message { - do { - let message = try Message(serializedBytes: serializedMessageBytes) - return message - } catch let error { - throw RPCError( - code: .invalidArgument, - message: "Can't deserialize to message of type \(Message.self)", - cause: error - ) - } - } -} diff --git a/Sources/GRPCProtobufCodeGen/ProtobufCodeGenParser.swift b/Sources/GRPCProtobufCodeGen/ProtobufCodeGenParser.swift deleted file mode 100644 index 837cb2ce8..000000000 --- a/Sources/GRPCProtobufCodeGen/ProtobufCodeGenParser.swift +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -internal import Foundation -internal import SwiftProtobuf -internal import SwiftProtobufPluginLibrary - -internal import struct GRPCCodeGen.CodeGenerationRequest -internal import struct GRPCCodeGen.SourceGenerator - -/// Parses a ``FileDescriptor`` object into a ``CodeGenerationRequest`` object. -internal struct ProtobufCodeGenParser { - let input: FileDescriptor - let namer: SwiftProtobufNamer - let extraModuleImports: [String] - let protoToModuleMappings: ProtoFileToModuleMappings - let accessLevel: SourceGenerator.Config.AccessLevel - - internal init( - input: FileDescriptor, - protoFileModuleMappings: ProtoFileToModuleMappings, - extraModuleImports: [String], - accessLevel: SourceGenerator.Config.AccessLevel - ) { - self.input = input - self.extraModuleImports = extraModuleImports - self.protoToModuleMappings = protoFileModuleMappings - self.namer = SwiftProtobufNamer( - currentFile: input, - protoFileToModuleMappings: protoFileModuleMappings - ) - self.accessLevel = accessLevel - } - - internal func parse() throws -> CodeGenerationRequest { - var header = self.input.header - // Ensuring there is a blank line after the header. - if !header.isEmpty && !header.hasSuffix("\n\n") { - header.append("\n") - } - let leadingTrivia = """ - // DO NOT EDIT. - // swift-format-ignore-file - // - // Generated by the gRPC Swift generator plugin for the protocol buffer compiler. - // Source: \(self.input.name) - // - // For information on using the generated types, please see the documentation: - // https://github.com/grpc/grpc-swift - - """ - let lookupSerializer: (String) -> String = { messageType in - "GRPCProtobuf.ProtobufSerializer<\(messageType)>()" - } - let lookupDeserializer: (String) -> String = { messageType in - "GRPCProtobuf.ProtobufDeserializer<\(messageType)>()" - } - let services = self.input.services.map { - CodeGenerationRequest.ServiceDescriptor( - descriptor: $0, - package: input.package, - protobufNamer: self.namer, - file: self.input - ) - } - - return CodeGenerationRequest( - fileName: self.input.name, - leadingTrivia: header + leadingTrivia, - dependencies: self.codeDependencies, - services: services, - lookupSerializer: lookupSerializer, - lookupDeserializer: lookupDeserializer - ) - } -} - -extension ProtobufCodeGenParser { - fileprivate var codeDependencies: [CodeGenerationRequest.Dependency] { - var codeDependencies: [CodeGenerationRequest.Dependency] = [ - .init(module: "GRPCProtobuf", accessLevel: .internal) - ] - // Adding as dependencies the modules containing generated code or types for - // '.proto' files imported in the '.proto' file we are parsing. - codeDependencies.append( - contentsOf: (self.protoToModuleMappings.neededModules(forFile: self.input) ?? []).map { - CodeGenerationRequest.Dependency(module: $0, accessLevel: self.accessLevel) - } - ) - // Adding extra imports passed in as an option to the plugin. - codeDependencies.append( - contentsOf: self.extraModuleImports.sorted().map { - CodeGenerationRequest.Dependency(module: $0, accessLevel: self.accessLevel) - } - ) - return codeDependencies - } -} - -extension CodeGenerationRequest.ServiceDescriptor { - fileprivate init( - descriptor: ServiceDescriptor, - package: String, - protobufNamer: SwiftProtobufNamer, - file: FileDescriptor - ) { - let methods = descriptor.methods.map { - CodeGenerationRequest.ServiceDescriptor.MethodDescriptor( - descriptor: $0, - protobufNamer: protobufNamer - ) - } - let name = CodeGenerationRequest.Name( - base: descriptor.name, - generatedUpperCase: NamingUtils.toUpperCamelCase(descriptor.name), - generatedLowerCase: NamingUtils.toLowerCamelCase(descriptor.name) - ) - - // Packages that are based on the path of the '.proto' file usually - // contain dots. For example: "grpc.test". - let namespace = CodeGenerationRequest.Name( - base: package, - generatedUpperCase: protobufNamer.formattedUpperCasePackage(file: file), - generatedLowerCase: protobufNamer.formattedLowerCasePackage(file: file) - ) - let documentation = descriptor.protoSourceComments() - self.init(documentation: documentation, name: name, namespace: namespace, methods: methods) - } -} - -extension CodeGenerationRequest.ServiceDescriptor.MethodDescriptor { - fileprivate init(descriptor: MethodDescriptor, protobufNamer: SwiftProtobufNamer) { - let name = CodeGenerationRequest.Name( - base: descriptor.name, - generatedUpperCase: NamingUtils.toUpperCamelCase(descriptor.name), - generatedLowerCase: NamingUtils.toLowerCamelCase(descriptor.name) - ) - let documentation = descriptor.protoSourceComments() - self.init( - documentation: documentation, - name: name, - isInputStreaming: descriptor.clientStreaming, - isOutputStreaming: descriptor.serverStreaming, - inputType: protobufNamer.fullName(message: descriptor.inputType), - outputType: protobufNamer.fullName(message: descriptor.outputType) - ) - } -} - -extension FileDescriptor { - fileprivate var header: String { - var header = String() - // Field number used to collect the syntax field which is usually the first - // declaration in a.proto file. - // See more here: - // https://github.com/apple/swift-protobuf/blob/main/Protos/SwiftProtobuf/google/protobuf/descriptor.proto - let syntaxPath = IndexPath(index: 12) - if let syntaxLocation = self.sourceCodeInfoLocation(path: syntaxPath) { - header = syntaxLocation.asSourceComment( - commentPrefix: "///", - leadingDetachedPrefix: "//" - ) - } - return header - } -} - -extension SwiftProtobufNamer { - internal func formattedUpperCasePackage(file: FileDescriptor) -> String { - let unformattedPackage = self.typePrefix(forFile: file) - return unformattedPackage.trimTrailingUnderscores() - } - - internal func formattedLowerCasePackage(file: FileDescriptor) -> String { - let upperCasePackage = self.formattedUpperCasePackage(file: file) - let lowerCaseComponents = upperCasePackage.split(separator: "_").map { component in - NamingUtils.toLowerCamelCase(String(component)) - } - return lowerCaseComponents.joined(separator: "_") - } -} - -extension String { - internal func trimTrailingUnderscores() -> String { - if let index = self.lastIndex(where: { $0 != "_" }) { - return String(self[...index]) - } else { - return "" - } - } -} diff --git a/Sources/GRPCProtobufCodeGen/ProtobufCodeGenerator.swift b/Sources/GRPCProtobufCodeGen/ProtobufCodeGenerator.swift deleted file mode 100644 index ad888319d..000000000 --- a/Sources/GRPCProtobufCodeGen/ProtobufCodeGenerator.swift +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -public import GRPCCodeGen -public import SwiftProtobufPluginLibrary - -public struct ProtobufCodeGenerator { - internal var configuration: SourceGenerator.Config - - public init( - configuration: SourceGenerator.Config - ) { - self.configuration = configuration - } - - public func generateCode( - from fileDescriptor: FileDescriptor, - protoFileModuleMappings: ProtoFileToModuleMappings, - extraModuleImports: [String] - ) throws -> String { - let parser = ProtobufCodeGenParser( - input: fileDescriptor, - protoFileModuleMappings: protoFileModuleMappings, - extraModuleImports: extraModuleImports, - accessLevel: self.configuration.accessLevel - ) - let sourceGenerator = SourceGenerator(config: self.configuration) - - let codeGenerationRequest = try parser.parse() - let sourceFile = try sourceGenerator.generate(codeGenerationRequest) - return sourceFile.contents - } -} diff --git a/Sources/InteroperabilityTests/AssertionFailure.swift b/Sources/InteroperabilityTests/AssertionFailure.swift deleted file mode 100644 index 112a36ee3..000000000 --- a/Sources/InteroperabilityTests/AssertionFailure.swift +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -/// Failure assertion for interoperability testing. -/// -/// This is required because the tests must be able to run without XCTest. -public struct AssertionFailure: Error { - public var message: String - public var file: String - public var line: Int - - public init(message: String, file: String = #fileID, line: Int = #line) { - self.message = message - self.file = file - self.line = line - } -} - -/// Asserts that the value of an expression is `true`. -public func assertTrue( - _ expression: @autoclosure () throws -> Bool, - _ message: String = "The statement is not true.", - file: String = #fileID, - line: Int = #line -) throws { - guard try expression() else { - throw AssertionFailure(message: message, file: file, line: line) - } -} - -/// Asserts that the two given values are equal. -public func assertEqual( - _ value1: T, - _ value2: T, - file: String = #fileID, - line: Int = #line -) throws { - return try assertTrue( - value1 == value2, - "'\(value1)' is not equal to '\(value2)'", - file: file, - line: line - ) -} diff --git a/Sources/InteroperabilityTests/Generated/empty.pb.swift b/Sources/InteroperabilityTests/Generated/empty.pb.swift deleted file mode 100644 index 7e246bf7b..000000000 --- a/Sources/InteroperabilityTests/Generated/empty.pb.swift +++ /dev/null @@ -1,75 +0,0 @@ -// DO NOT EDIT. -// swift-format-ignore-file -// swiftlint:disable all -// -// Generated by the Swift generator plugin for the protocol buffer compiler. -// Source: src/proto/grpc/testing/empty.proto -// -// For information on using the generated types, please see the documentation: -// https://github.com/apple/swift-protobuf/ - -// Copyright 2015 gRPC authors. -// -// 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. - -public import SwiftProtobuf - -// If the compiler emits an error on this type, it is because this file -// was generated by a version of the `protoc` Swift plug-in that is -// incompatible with the version of SwiftProtobuf to which you are linking. -// Please ensure that you are building against the same version of the API -// that was used to generate this file. -fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { - struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} - typealias Version = _2 -} - -/// An empty message that you can re-use to avoid defining duplicated empty -/// messages in your project. A typical example is to use it as argument or the -/// return value of a service API. For instance: -/// -/// service Foo { -/// rpc Bar (grpc.testing.Empty) returns (grpc.testing.Empty) { }; -/// }; -public struct Grpc_Testing_Empty: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - public var unknownFields = SwiftProtobuf.UnknownStorage() - - public init() {} -} - -// MARK: - Code below here is support for the SwiftProtobuf runtime. - -fileprivate let _protobuf_package = "grpc.testing" - -extension Grpc_Testing_Empty: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = _protobuf_package + ".Empty" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap() - - public mutating func decodeMessage(decoder: inout D) throws { - // Load everything into unknown fields - while try decoder.nextFieldNumber() != nil {} - } - - public func traverse(visitor: inout V) throws { - try unknownFields.traverse(visitor: &visitor) - } - - public static func ==(lhs: Grpc_Testing_Empty, rhs: Grpc_Testing_Empty) -> Bool { - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} diff --git a/Sources/InteroperabilityTests/Generated/empty_service.grpc.swift b/Sources/InteroperabilityTests/Generated/empty_service.grpc.swift deleted file mode 100644 index ede7a37ea..000000000 --- a/Sources/InteroperabilityTests/Generated/empty_service.grpc.swift +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright 2018 gRPC authors. -// -// 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. - -// DO NOT EDIT. -// swift-format-ignore-file -// -// Generated by the gRPC Swift generator plugin for the protocol buffer compiler. -// Source: src/proto/grpc/testing/empty_service.proto -// -// For information on using the generated types, please see the documentation: -// https://github.com/grpc/grpc-swift - -public import GRPCCore -internal import GRPCProtobuf - -public enum Grpc_Testing_EmptyService { - public static let descriptor = GRPCCore.ServiceDescriptor.grpc_testing_EmptyService - public enum Method { - public static let descriptors: [GRPCCore.MethodDescriptor] = [] - } - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public typealias StreamingServiceProtocol = Grpc_Testing_EmptyServiceStreamingServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public typealias ServiceProtocol = Grpc_Testing_EmptyServiceServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public typealias ClientProtocol = Grpc_Testing_EmptyServiceClientProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public typealias Client = Grpc_Testing_EmptyServiceClient -} - -extension GRPCCore.ServiceDescriptor { - public static let grpc_testing_EmptyService = Self( - package: "grpc.testing", - service: "EmptyService" - ) -} - -/// A service that has zero methods. -/// See https://github.com/grpc/grpc/issues/15574 -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -public protocol Grpc_Testing_EmptyServiceStreamingServiceProtocol: GRPCCore.RegistrableRPCService {} - -/// Conformance to `GRPCCore.RegistrableRPCService`. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension Grpc_Testing_EmptyService.StreamingServiceProtocol { - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public func registerMethods(with router: inout GRPCCore.RPCRouter) {} -} - -/// A service that has zero methods. -/// See https://github.com/grpc/grpc/issues/15574 -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -public protocol Grpc_Testing_EmptyServiceServiceProtocol: Grpc_Testing_EmptyService.StreamingServiceProtocol {} - -/// Partial conformance to `Grpc_Testing_EmptyServiceStreamingServiceProtocol`. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension Grpc_Testing_EmptyService.ServiceProtocol { -} - -/// A service that has zero methods. -/// See https://github.com/grpc/grpc/issues/15574 -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -public protocol Grpc_Testing_EmptyServiceClientProtocol: Sendable {} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension Grpc_Testing_EmptyService.ClientProtocol { -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension Grpc_Testing_EmptyService.ClientProtocol { -} - -/// A service that has zero methods. -/// See https://github.com/grpc/grpc/issues/15574 -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -public struct Grpc_Testing_EmptyServiceClient: Grpc_Testing_EmptyService.ClientProtocol { - private let client: GRPCCore.GRPCClient - - public init(wrapping client: GRPCCore.GRPCClient) { - self.client = client - } -} \ No newline at end of file diff --git a/Sources/InteroperabilityTests/Generated/empty_service.pb.swift b/Sources/InteroperabilityTests/Generated/empty_service.pb.swift deleted file mode 100644 index 81eecc29e..000000000 --- a/Sources/InteroperabilityTests/Generated/empty_service.pb.swift +++ /dev/null @@ -1,25 +0,0 @@ -// DO NOT EDIT. -// swift-format-ignore-file -// swiftlint:disable all -// -// Generated by the Swift generator plugin for the protocol buffer compiler. -// Source: src/proto/grpc/testing/empty_service.proto -// -// For information on using the generated types, please see the documentation: -// https://github.com/apple/swift-protobuf/ - -// Copyright 2018 gRPC authors. -// -// 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. - -// This file contained no messages, enums, or extensions. diff --git a/Sources/InteroperabilityTests/Generated/messages.pb.swift b/Sources/InteroperabilityTests/Generated/messages.pb.swift deleted file mode 100644 index cd0a3dd15..000000000 --- a/Sources/InteroperabilityTests/Generated/messages.pb.swift +++ /dev/null @@ -1,929 +0,0 @@ -// DO NOT EDIT. -// swift-format-ignore-file -// swiftlint:disable all -// -// Generated by the Swift generator plugin for the protocol buffer compiler. -// Source: src/proto/grpc/testing/messages.proto -// -// For information on using the generated types, please see the documentation: -// https://github.com/apple/swift-protobuf/ - -// Copyright 2015-2016 gRPC authors. -// -// 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. - -// Message definitions to be used by integration test service definitions. - -public import Foundation -public import SwiftProtobuf - -// If the compiler emits an error on this type, it is because this file -// was generated by a version of the `protoc` Swift plug-in that is -// incompatible with the version of SwiftProtobuf to which you are linking. -// Please ensure that you are building against the same version of the API -// that was used to generate this file. -fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { - struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} - typealias Version = _2 -} - -/// The type of payload that should be returned. -public enum Grpc_Testing_PayloadType: SwiftProtobuf.Enum, Swift.CaseIterable { - public typealias RawValue = Int - - /// Compressable text format. - case compressable // = 0 - case UNRECOGNIZED(Int) - - public init() { - self = .compressable - } - - public init?(rawValue: Int) { - switch rawValue { - case 0: self = .compressable - default: self = .UNRECOGNIZED(rawValue) - } - } - - public var rawValue: Int { - switch self { - case .compressable: return 0 - case .UNRECOGNIZED(let i): return i - } - } - - // The compiler won't synthesize support with the UNRECOGNIZED case. - public static let allCases: [Grpc_Testing_PayloadType] = [ - .compressable, - ] - -} - -/// TODO(dgq): Go back to using well-known types once -/// https://github.com/grpc/grpc/issues/6980 has been fixed. -/// import "google/protobuf/wrappers.proto"; -public struct Grpc_Testing_BoolValue: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// The bool value. - public var value: Bool = false - - public var unknownFields = SwiftProtobuf.UnknownStorage() - - public init() {} -} - -/// A block of data, to simply increase gRPC message size. -public struct Grpc_Testing_Payload: @unchecked Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// The type of data in body. - public var type: Grpc_Testing_PayloadType = .compressable - - /// Primary contents of payload. - public var body: Data = Data() - - public var unknownFields = SwiftProtobuf.UnknownStorage() - - public init() {} -} - -/// A protobuf representation for grpc status. This is used by test -/// clients to specify a status that the server should attempt to return. -public struct Grpc_Testing_EchoStatus: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - public var code: Int32 = 0 - - public var message: String = String() - - public var unknownFields = SwiftProtobuf.UnknownStorage() - - public init() {} -} - -/// Unary request. -public struct Grpc_Testing_SimpleRequest: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// Desired payload type in the response from the server. - /// If response_type is RANDOM, server randomly chooses one from other formats. - public var responseType: Grpc_Testing_PayloadType = .compressable - - /// Desired payload size in the response from the server. - public var responseSize: Int32 = 0 - - /// Optional input payload sent along with the request. - public var payload: Grpc_Testing_Payload { - get {return _payload ?? Grpc_Testing_Payload()} - set {_payload = newValue} - } - /// Returns true if `payload` has been explicitly set. - public var hasPayload: Bool {return self._payload != nil} - /// Clears the value of `payload`. Subsequent reads from it will return its default value. - public mutating func clearPayload() {self._payload = nil} - - /// Whether SimpleResponse should include username. - public var fillUsername: Bool = false - - /// Whether SimpleResponse should include OAuth scope. - public var fillOauthScope: Bool = false - - /// Whether to request the server to compress the response. This field is - /// "nullable" in order to interoperate seamlessly with clients not able to - /// implement the full compression tests by introspecting the call to verify - /// the response's compression status. - public var responseCompressed: Grpc_Testing_BoolValue { - get {return _responseCompressed ?? Grpc_Testing_BoolValue()} - set {_responseCompressed = newValue} - } - /// Returns true if `responseCompressed` has been explicitly set. - public var hasResponseCompressed: Bool {return self._responseCompressed != nil} - /// Clears the value of `responseCompressed`. Subsequent reads from it will return its default value. - public mutating func clearResponseCompressed() {self._responseCompressed = nil} - - /// Whether server should return a given status - public var responseStatus: Grpc_Testing_EchoStatus { - get {return _responseStatus ?? Grpc_Testing_EchoStatus()} - set {_responseStatus = newValue} - } - /// Returns true if `responseStatus` has been explicitly set. - public var hasResponseStatus: Bool {return self._responseStatus != nil} - /// Clears the value of `responseStatus`. Subsequent reads from it will return its default value. - public mutating func clearResponseStatus() {self._responseStatus = nil} - - /// Whether the server should expect this request to be compressed. - public var expectCompressed: Grpc_Testing_BoolValue { - get {return _expectCompressed ?? Grpc_Testing_BoolValue()} - set {_expectCompressed = newValue} - } - /// Returns true if `expectCompressed` has been explicitly set. - public var hasExpectCompressed: Bool {return self._expectCompressed != nil} - /// Clears the value of `expectCompressed`. Subsequent reads from it will return its default value. - public mutating func clearExpectCompressed() {self._expectCompressed = nil} - - public var unknownFields = SwiftProtobuf.UnknownStorage() - - public init() {} - - fileprivate var _payload: Grpc_Testing_Payload? = nil - fileprivate var _responseCompressed: Grpc_Testing_BoolValue? = nil - fileprivate var _responseStatus: Grpc_Testing_EchoStatus? = nil - fileprivate var _expectCompressed: Grpc_Testing_BoolValue? = nil -} - -/// Unary response, as configured by the request. -public struct Grpc_Testing_SimpleResponse: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// Payload to increase message size. - public var payload: Grpc_Testing_Payload { - get {return _payload ?? Grpc_Testing_Payload()} - set {_payload = newValue} - } - /// Returns true if `payload` has been explicitly set. - public var hasPayload: Bool {return self._payload != nil} - /// Clears the value of `payload`. Subsequent reads from it will return its default value. - public mutating func clearPayload() {self._payload = nil} - - /// The user the request came from, for verifying authentication was - /// successful when the client expected it. - public var username: String = String() - - /// OAuth scope. - public var oauthScope: String = String() - - public var unknownFields = SwiftProtobuf.UnknownStorage() - - public init() {} - - fileprivate var _payload: Grpc_Testing_Payload? = nil -} - -/// Client-streaming request. -public struct Grpc_Testing_StreamingInputCallRequest: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// Optional input payload sent along with the request. - public var payload: Grpc_Testing_Payload { - get {return _payload ?? Grpc_Testing_Payload()} - set {_payload = newValue} - } - /// Returns true if `payload` has been explicitly set. - public var hasPayload: Bool {return self._payload != nil} - /// Clears the value of `payload`. Subsequent reads from it will return its default value. - public mutating func clearPayload() {self._payload = nil} - - /// Whether the server should expect this request to be compressed. This field - /// is "nullable" in order to interoperate seamlessly with servers not able to - /// implement the full compression tests by introspecting the call to verify - /// the request's compression status. - public var expectCompressed: Grpc_Testing_BoolValue { - get {return _expectCompressed ?? Grpc_Testing_BoolValue()} - set {_expectCompressed = newValue} - } - /// Returns true if `expectCompressed` has been explicitly set. - public var hasExpectCompressed: Bool {return self._expectCompressed != nil} - /// Clears the value of `expectCompressed`. Subsequent reads from it will return its default value. - public mutating func clearExpectCompressed() {self._expectCompressed = nil} - - public var unknownFields = SwiftProtobuf.UnknownStorage() - - public init() {} - - fileprivate var _payload: Grpc_Testing_Payload? = nil - fileprivate var _expectCompressed: Grpc_Testing_BoolValue? = nil -} - -/// Client-streaming response. -public struct Grpc_Testing_StreamingInputCallResponse: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// Aggregated size of payloads received from the client. - public var aggregatedPayloadSize: Int32 = 0 - - public var unknownFields = SwiftProtobuf.UnknownStorage() - - public init() {} -} - -/// Configuration for a particular response. -public struct Grpc_Testing_ResponseParameters: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// Desired payload sizes in responses from the server. - public var size: Int32 = 0 - - /// Desired interval between consecutive responses in the response stream in - /// microseconds. - public var intervalUs: Int32 = 0 - - /// Whether to request the server to compress the response. This field is - /// "nullable" in order to interoperate seamlessly with clients not able to - /// implement the full compression tests by introspecting the call to verify - /// the response's compression status. - public var compressed: Grpc_Testing_BoolValue { - get {return _compressed ?? Grpc_Testing_BoolValue()} - set {_compressed = newValue} - } - /// Returns true if `compressed` has been explicitly set. - public var hasCompressed: Bool {return self._compressed != nil} - /// Clears the value of `compressed`. Subsequent reads from it will return its default value. - public mutating func clearCompressed() {self._compressed = nil} - - public var unknownFields = SwiftProtobuf.UnknownStorage() - - public init() {} - - fileprivate var _compressed: Grpc_Testing_BoolValue? = nil -} - -/// Server-streaming request. -public struct Grpc_Testing_StreamingOutputCallRequest: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// Desired payload type in the response from the server. - /// If response_type is RANDOM, the payload from each response in the stream - /// might be of different types. This is to simulate a mixed type of payload - /// stream. - public var responseType: Grpc_Testing_PayloadType = .compressable - - /// Configuration for each expected response message. - public var responseParameters: [Grpc_Testing_ResponseParameters] = [] - - /// Optional input payload sent along with the request. - public var payload: Grpc_Testing_Payload { - get {return _payload ?? Grpc_Testing_Payload()} - set {_payload = newValue} - } - /// Returns true if `payload` has been explicitly set. - public var hasPayload: Bool {return self._payload != nil} - /// Clears the value of `payload`. Subsequent reads from it will return its default value. - public mutating func clearPayload() {self._payload = nil} - - /// Whether server should return a given status - public var responseStatus: Grpc_Testing_EchoStatus { - get {return _responseStatus ?? Grpc_Testing_EchoStatus()} - set {_responseStatus = newValue} - } - /// Returns true if `responseStatus` has been explicitly set. - public var hasResponseStatus: Bool {return self._responseStatus != nil} - /// Clears the value of `responseStatus`. Subsequent reads from it will return its default value. - public mutating func clearResponseStatus() {self._responseStatus = nil} - - public var unknownFields = SwiftProtobuf.UnknownStorage() - - public init() {} - - fileprivate var _payload: Grpc_Testing_Payload? = nil - fileprivate var _responseStatus: Grpc_Testing_EchoStatus? = nil -} - -/// Server-streaming response, as configured by the request and parameters. -public struct Grpc_Testing_StreamingOutputCallResponse: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// Payload to increase response size. - public var payload: Grpc_Testing_Payload { - get {return _payload ?? Grpc_Testing_Payload()} - set {_payload = newValue} - } - /// Returns true if `payload` has been explicitly set. - public var hasPayload: Bool {return self._payload != nil} - /// Clears the value of `payload`. Subsequent reads from it will return its default value. - public mutating func clearPayload() {self._payload = nil} - - public var unknownFields = SwiftProtobuf.UnknownStorage() - - public init() {} - - fileprivate var _payload: Grpc_Testing_Payload? = nil -} - -/// For reconnect interop test only. -/// Client tells server what reconnection parameters it used. -public struct Grpc_Testing_ReconnectParams: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - public var maxReconnectBackoffMs: Int32 = 0 - - public var unknownFields = SwiftProtobuf.UnknownStorage() - - public init() {} -} - -/// For reconnect interop test only. -/// Server tells client whether its reconnects are following the spec and the -/// reconnect backoffs it saw. -public struct Grpc_Testing_ReconnectInfo: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - public var passed: Bool = false - - public var backoffMs: [Int32] = [] - - public var unknownFields = SwiftProtobuf.UnknownStorage() - - public init() {} -} - -// MARK: - Code below here is support for the SwiftProtobuf runtime. - -fileprivate let _protobuf_package = "grpc.testing" - -extension Grpc_Testing_PayloadType: SwiftProtobuf._ProtoNameProviding { - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 0: .same(proto: "COMPRESSABLE"), - ] -} - -extension Grpc_Testing_BoolValue: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = _protobuf_package + ".BoolValue" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "value"), - ] - - public mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularBoolField(value: &self.value) }() - default: break - } - } - } - - public func traverse(visitor: inout V) throws { - if self.value != false { - try visitor.visitSingularBoolField(value: self.value, fieldNumber: 1) - } - try unknownFields.traverse(visitor: &visitor) - } - - public static func ==(lhs: Grpc_Testing_BoolValue, rhs: Grpc_Testing_BoolValue) -> Bool { - if lhs.value != rhs.value {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_Payload: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = _protobuf_package + ".Payload" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "type"), - 2: .same(proto: "body"), - ] - - public mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularEnumField(value: &self.type) }() - case 2: try { try decoder.decodeSingularBytesField(value: &self.body) }() - default: break - } - } - } - - public func traverse(visitor: inout V) throws { - if self.type != .compressable { - try visitor.visitSingularEnumField(value: self.type, fieldNumber: 1) - } - if !self.body.isEmpty { - try visitor.visitSingularBytesField(value: self.body, fieldNumber: 2) - } - try unknownFields.traverse(visitor: &visitor) - } - - public static func ==(lhs: Grpc_Testing_Payload, rhs: Grpc_Testing_Payload) -> Bool { - if lhs.type != rhs.type {return false} - if lhs.body != rhs.body {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_EchoStatus: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = _protobuf_package + ".EchoStatus" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "code"), - 2: .same(proto: "message"), - ] - - public mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularInt32Field(value: &self.code) }() - case 2: try { try decoder.decodeSingularStringField(value: &self.message) }() - default: break - } - } - } - - public func traverse(visitor: inout V) throws { - if self.code != 0 { - try visitor.visitSingularInt32Field(value: self.code, fieldNumber: 1) - } - if !self.message.isEmpty { - try visitor.visitSingularStringField(value: self.message, fieldNumber: 2) - } - try unknownFields.traverse(visitor: &visitor) - } - - public static func ==(lhs: Grpc_Testing_EchoStatus, rhs: Grpc_Testing_EchoStatus) -> Bool { - if lhs.code != rhs.code {return false} - if lhs.message != rhs.message {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_SimpleRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = _protobuf_package + ".SimpleRequest" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "response_type"), - 2: .standard(proto: "response_size"), - 3: .same(proto: "payload"), - 4: .standard(proto: "fill_username"), - 5: .standard(proto: "fill_oauth_scope"), - 6: .standard(proto: "response_compressed"), - 7: .standard(proto: "response_status"), - 8: .standard(proto: "expect_compressed"), - ] - - public mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularEnumField(value: &self.responseType) }() - case 2: try { try decoder.decodeSingularInt32Field(value: &self.responseSize) }() - case 3: try { try decoder.decodeSingularMessageField(value: &self._payload) }() - case 4: try { try decoder.decodeSingularBoolField(value: &self.fillUsername) }() - case 5: try { try decoder.decodeSingularBoolField(value: &self.fillOauthScope) }() - case 6: try { try decoder.decodeSingularMessageField(value: &self._responseCompressed) }() - case 7: try { try decoder.decodeSingularMessageField(value: &self._responseStatus) }() - case 8: try { try decoder.decodeSingularMessageField(value: &self._expectCompressed) }() - default: break - } - } - } - - public func traverse(visitor: inout V) throws { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 - if self.responseType != .compressable { - try visitor.visitSingularEnumField(value: self.responseType, fieldNumber: 1) - } - if self.responseSize != 0 { - try visitor.visitSingularInt32Field(value: self.responseSize, fieldNumber: 2) - } - try { if let v = self._payload { - try visitor.visitSingularMessageField(value: v, fieldNumber: 3) - } }() - if self.fillUsername != false { - try visitor.visitSingularBoolField(value: self.fillUsername, fieldNumber: 4) - } - if self.fillOauthScope != false { - try visitor.visitSingularBoolField(value: self.fillOauthScope, fieldNumber: 5) - } - try { if let v = self._responseCompressed { - try visitor.visitSingularMessageField(value: v, fieldNumber: 6) - } }() - try { if let v = self._responseStatus { - try visitor.visitSingularMessageField(value: v, fieldNumber: 7) - } }() - try { if let v = self._expectCompressed { - try visitor.visitSingularMessageField(value: v, fieldNumber: 8) - } }() - try unknownFields.traverse(visitor: &visitor) - } - - public static func ==(lhs: Grpc_Testing_SimpleRequest, rhs: Grpc_Testing_SimpleRequest) -> Bool { - if lhs.responseType != rhs.responseType {return false} - if lhs.responseSize != rhs.responseSize {return false} - if lhs._payload != rhs._payload {return false} - if lhs.fillUsername != rhs.fillUsername {return false} - if lhs.fillOauthScope != rhs.fillOauthScope {return false} - if lhs._responseCompressed != rhs._responseCompressed {return false} - if lhs._responseStatus != rhs._responseStatus {return false} - if lhs._expectCompressed != rhs._expectCompressed {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_SimpleResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = _protobuf_package + ".SimpleResponse" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "payload"), - 2: .same(proto: "username"), - 3: .standard(proto: "oauth_scope"), - ] - - public mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularMessageField(value: &self._payload) }() - case 2: try { try decoder.decodeSingularStringField(value: &self.username) }() - case 3: try { try decoder.decodeSingularStringField(value: &self.oauthScope) }() - default: break - } - } - } - - public func traverse(visitor: inout V) throws { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 - try { if let v = self._payload { - try visitor.visitSingularMessageField(value: v, fieldNumber: 1) - } }() - if !self.username.isEmpty { - try visitor.visitSingularStringField(value: self.username, fieldNumber: 2) - } - if !self.oauthScope.isEmpty { - try visitor.visitSingularStringField(value: self.oauthScope, fieldNumber: 3) - } - try unknownFields.traverse(visitor: &visitor) - } - - public static func ==(lhs: Grpc_Testing_SimpleResponse, rhs: Grpc_Testing_SimpleResponse) -> Bool { - if lhs._payload != rhs._payload {return false} - if lhs.username != rhs.username {return false} - if lhs.oauthScope != rhs.oauthScope {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_StreamingInputCallRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = _protobuf_package + ".StreamingInputCallRequest" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "payload"), - 2: .standard(proto: "expect_compressed"), - ] - - public mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularMessageField(value: &self._payload) }() - case 2: try { try decoder.decodeSingularMessageField(value: &self._expectCompressed) }() - default: break - } - } - } - - public func traverse(visitor: inout V) throws { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 - try { if let v = self._payload { - try visitor.visitSingularMessageField(value: v, fieldNumber: 1) - } }() - try { if let v = self._expectCompressed { - try visitor.visitSingularMessageField(value: v, fieldNumber: 2) - } }() - try unknownFields.traverse(visitor: &visitor) - } - - public static func ==(lhs: Grpc_Testing_StreamingInputCallRequest, rhs: Grpc_Testing_StreamingInputCallRequest) -> Bool { - if lhs._payload != rhs._payload {return false} - if lhs._expectCompressed != rhs._expectCompressed {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_StreamingInputCallResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = _protobuf_package + ".StreamingInputCallResponse" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "aggregated_payload_size"), - ] - - public mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularInt32Field(value: &self.aggregatedPayloadSize) }() - default: break - } - } - } - - public func traverse(visitor: inout V) throws { - if self.aggregatedPayloadSize != 0 { - try visitor.visitSingularInt32Field(value: self.aggregatedPayloadSize, fieldNumber: 1) - } - try unknownFields.traverse(visitor: &visitor) - } - - public static func ==(lhs: Grpc_Testing_StreamingInputCallResponse, rhs: Grpc_Testing_StreamingInputCallResponse) -> Bool { - if lhs.aggregatedPayloadSize != rhs.aggregatedPayloadSize {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_ResponseParameters: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = _protobuf_package + ".ResponseParameters" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "size"), - 2: .standard(proto: "interval_us"), - 3: .same(proto: "compressed"), - ] - - public mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularInt32Field(value: &self.size) }() - case 2: try { try decoder.decodeSingularInt32Field(value: &self.intervalUs) }() - case 3: try { try decoder.decodeSingularMessageField(value: &self._compressed) }() - default: break - } - } - } - - public func traverse(visitor: inout V) throws { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 - if self.size != 0 { - try visitor.visitSingularInt32Field(value: self.size, fieldNumber: 1) - } - if self.intervalUs != 0 { - try visitor.visitSingularInt32Field(value: self.intervalUs, fieldNumber: 2) - } - try { if let v = self._compressed { - try visitor.visitSingularMessageField(value: v, fieldNumber: 3) - } }() - try unknownFields.traverse(visitor: &visitor) - } - - public static func ==(lhs: Grpc_Testing_ResponseParameters, rhs: Grpc_Testing_ResponseParameters) -> Bool { - if lhs.size != rhs.size {return false} - if lhs.intervalUs != rhs.intervalUs {return false} - if lhs._compressed != rhs._compressed {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_StreamingOutputCallRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = _protobuf_package + ".StreamingOutputCallRequest" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "response_type"), - 2: .standard(proto: "response_parameters"), - 3: .same(proto: "payload"), - 7: .standard(proto: "response_status"), - ] - - public mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularEnumField(value: &self.responseType) }() - case 2: try { try decoder.decodeRepeatedMessageField(value: &self.responseParameters) }() - case 3: try { try decoder.decodeSingularMessageField(value: &self._payload) }() - case 7: try { try decoder.decodeSingularMessageField(value: &self._responseStatus) }() - default: break - } - } - } - - public func traverse(visitor: inout V) throws { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 - if self.responseType != .compressable { - try visitor.visitSingularEnumField(value: self.responseType, fieldNumber: 1) - } - if !self.responseParameters.isEmpty { - try visitor.visitRepeatedMessageField(value: self.responseParameters, fieldNumber: 2) - } - try { if let v = self._payload { - try visitor.visitSingularMessageField(value: v, fieldNumber: 3) - } }() - try { if let v = self._responseStatus { - try visitor.visitSingularMessageField(value: v, fieldNumber: 7) - } }() - try unknownFields.traverse(visitor: &visitor) - } - - public static func ==(lhs: Grpc_Testing_StreamingOutputCallRequest, rhs: Grpc_Testing_StreamingOutputCallRequest) -> Bool { - if lhs.responseType != rhs.responseType {return false} - if lhs.responseParameters != rhs.responseParameters {return false} - if lhs._payload != rhs._payload {return false} - if lhs._responseStatus != rhs._responseStatus {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_StreamingOutputCallResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = _protobuf_package + ".StreamingOutputCallResponse" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "payload"), - ] - - public mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularMessageField(value: &self._payload) }() - default: break - } - } - } - - public func traverse(visitor: inout V) throws { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 - try { if let v = self._payload { - try visitor.visitSingularMessageField(value: v, fieldNumber: 1) - } }() - try unknownFields.traverse(visitor: &visitor) - } - - public static func ==(lhs: Grpc_Testing_StreamingOutputCallResponse, rhs: Grpc_Testing_StreamingOutputCallResponse) -> Bool { - if lhs._payload != rhs._payload {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_ReconnectParams: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = _protobuf_package + ".ReconnectParams" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "max_reconnect_backoff_ms"), - ] - - public mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularInt32Field(value: &self.maxReconnectBackoffMs) }() - default: break - } - } - } - - public func traverse(visitor: inout V) throws { - if self.maxReconnectBackoffMs != 0 { - try visitor.visitSingularInt32Field(value: self.maxReconnectBackoffMs, fieldNumber: 1) - } - try unknownFields.traverse(visitor: &visitor) - } - - public static func ==(lhs: Grpc_Testing_ReconnectParams, rhs: Grpc_Testing_ReconnectParams) -> Bool { - if lhs.maxReconnectBackoffMs != rhs.maxReconnectBackoffMs {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_ReconnectInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - public static let protoMessageName: String = _protobuf_package + ".ReconnectInfo" - public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "passed"), - 2: .standard(proto: "backoff_ms"), - ] - - public mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularBoolField(value: &self.passed) }() - case 2: try { try decoder.decodeRepeatedInt32Field(value: &self.backoffMs) }() - default: break - } - } - } - - public func traverse(visitor: inout V) throws { - if self.passed != false { - try visitor.visitSingularBoolField(value: self.passed, fieldNumber: 1) - } - if !self.backoffMs.isEmpty { - try visitor.visitPackedInt32Field(value: self.backoffMs, fieldNumber: 2) - } - try unknownFields.traverse(visitor: &visitor) - } - - public static func ==(lhs: Grpc_Testing_ReconnectInfo, rhs: Grpc_Testing_ReconnectInfo) -> Bool { - if lhs.passed != rhs.passed {return false} - if lhs.backoffMs != rhs.backoffMs {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} diff --git a/Sources/InteroperabilityTests/Generated/test.grpc.swift b/Sources/InteroperabilityTests/Generated/test.grpc.swift deleted file mode 100644 index bbdbf3e49..000000000 --- a/Sources/InteroperabilityTests/Generated/test.grpc.swift +++ /dev/null @@ -1,1413 +0,0 @@ -// Copyright 2015-2016 gRPC authors. -// -// 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. - -// An integration test service that covers all the method signature permutations -// of unary/streaming requests/responses. - -// DO NOT EDIT. -// swift-format-ignore-file -// -// Generated by the gRPC Swift generator plugin for the protocol buffer compiler. -// Source: src/proto/grpc/testing/test.proto -// -// For information on using the generated types, please see the documentation: -// https://github.com/grpc/grpc-swift - -public import GRPCCore -internal import GRPCProtobuf - -public enum Grpc_Testing_ReconnectService { - public static let descriptor = GRPCCore.ServiceDescriptor.grpc_testing_ReconnectService - public enum Method { - public enum Start { - public typealias Input = Grpc_Testing_ReconnectParams - public typealias Output = Grpc_Testing_Empty - public static let descriptor = GRPCCore.MethodDescriptor( - service: Grpc_Testing_ReconnectService.descriptor.fullyQualifiedService, - method: "Start" - ) - } - public enum Stop { - public typealias Input = Grpc_Testing_Empty - public typealias Output = Grpc_Testing_ReconnectInfo - public static let descriptor = GRPCCore.MethodDescriptor( - service: Grpc_Testing_ReconnectService.descriptor.fullyQualifiedService, - method: "Stop" - ) - } - public static let descriptors: [GRPCCore.MethodDescriptor] = [ - Start.descriptor, - Stop.descriptor - ] - } - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public typealias StreamingServiceProtocol = Grpc_Testing_ReconnectServiceStreamingServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public typealias ServiceProtocol = Grpc_Testing_ReconnectServiceServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public typealias ClientProtocol = Grpc_Testing_ReconnectServiceClientProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public typealias Client = Grpc_Testing_ReconnectServiceClient -} - -extension GRPCCore.ServiceDescriptor { - public static let grpc_testing_ReconnectService = Self( - package: "grpc.testing", - service: "ReconnectService" - ) -} - -public enum Grpc_Testing_TestService { - public static let descriptor = GRPCCore.ServiceDescriptor.grpc_testing_TestService - public enum Method { - public enum EmptyCall { - public typealias Input = Grpc_Testing_Empty - public typealias Output = Grpc_Testing_Empty - public static let descriptor = GRPCCore.MethodDescriptor( - service: Grpc_Testing_TestService.descriptor.fullyQualifiedService, - method: "EmptyCall" - ) - } - public enum UnaryCall { - public typealias Input = Grpc_Testing_SimpleRequest - public typealias Output = Grpc_Testing_SimpleResponse - public static let descriptor = GRPCCore.MethodDescriptor( - service: Grpc_Testing_TestService.descriptor.fullyQualifiedService, - method: "UnaryCall" - ) - } - public enum CacheableUnaryCall { - public typealias Input = Grpc_Testing_SimpleRequest - public typealias Output = Grpc_Testing_SimpleResponse - public static let descriptor = GRPCCore.MethodDescriptor( - service: Grpc_Testing_TestService.descriptor.fullyQualifiedService, - method: "CacheableUnaryCall" - ) - } - public enum StreamingOutputCall { - public typealias Input = Grpc_Testing_StreamingOutputCallRequest - public typealias Output = Grpc_Testing_StreamingOutputCallResponse - public static let descriptor = GRPCCore.MethodDescriptor( - service: Grpc_Testing_TestService.descriptor.fullyQualifiedService, - method: "StreamingOutputCall" - ) - } - public enum StreamingInputCall { - public typealias Input = Grpc_Testing_StreamingInputCallRequest - public typealias Output = Grpc_Testing_StreamingInputCallResponse - public static let descriptor = GRPCCore.MethodDescriptor( - service: Grpc_Testing_TestService.descriptor.fullyQualifiedService, - method: "StreamingInputCall" - ) - } - public enum FullDuplexCall { - public typealias Input = Grpc_Testing_StreamingOutputCallRequest - public typealias Output = Grpc_Testing_StreamingOutputCallResponse - public static let descriptor = GRPCCore.MethodDescriptor( - service: Grpc_Testing_TestService.descriptor.fullyQualifiedService, - method: "FullDuplexCall" - ) - } - public enum HalfDuplexCall { - public typealias Input = Grpc_Testing_StreamingOutputCallRequest - public typealias Output = Grpc_Testing_StreamingOutputCallResponse - public static let descriptor = GRPCCore.MethodDescriptor( - service: Grpc_Testing_TestService.descriptor.fullyQualifiedService, - method: "HalfDuplexCall" - ) - } - public enum UnimplementedCall { - public typealias Input = Grpc_Testing_Empty - public typealias Output = Grpc_Testing_Empty - public static let descriptor = GRPCCore.MethodDescriptor( - service: Grpc_Testing_TestService.descriptor.fullyQualifiedService, - method: "UnimplementedCall" - ) - } - public static let descriptors: [GRPCCore.MethodDescriptor] = [ - EmptyCall.descriptor, - UnaryCall.descriptor, - CacheableUnaryCall.descriptor, - StreamingOutputCall.descriptor, - StreamingInputCall.descriptor, - FullDuplexCall.descriptor, - HalfDuplexCall.descriptor, - UnimplementedCall.descriptor - ] - } - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public typealias StreamingServiceProtocol = Grpc_Testing_TestServiceStreamingServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public typealias ServiceProtocol = Grpc_Testing_TestServiceServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public typealias ClientProtocol = Grpc_Testing_TestServiceClientProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public typealias Client = Grpc_Testing_TestServiceClient -} - -extension GRPCCore.ServiceDescriptor { - public static let grpc_testing_TestService = Self( - package: "grpc.testing", - service: "TestService" - ) -} - -public enum Grpc_Testing_UnimplementedService { - public static let descriptor = GRPCCore.ServiceDescriptor.grpc_testing_UnimplementedService - public enum Method { - public enum UnimplementedCall { - public typealias Input = Grpc_Testing_Empty - public typealias Output = Grpc_Testing_Empty - public static let descriptor = GRPCCore.MethodDescriptor( - service: Grpc_Testing_UnimplementedService.descriptor.fullyQualifiedService, - method: "UnimplementedCall" - ) - } - public static let descriptors: [GRPCCore.MethodDescriptor] = [ - UnimplementedCall.descriptor - ] - } - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public typealias StreamingServiceProtocol = Grpc_Testing_UnimplementedServiceStreamingServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public typealias ServiceProtocol = Grpc_Testing_UnimplementedServiceServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public typealias ClientProtocol = Grpc_Testing_UnimplementedServiceClientProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public typealias Client = Grpc_Testing_UnimplementedServiceClient -} - -extension GRPCCore.ServiceDescriptor { - public static let grpc_testing_UnimplementedService = Self( - package: "grpc.testing", - service: "UnimplementedService" - ) -} - -/// A simple service to test the various types of RPCs and experiment with -/// performance with various types of payload. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -public protocol Grpc_Testing_TestServiceStreamingServiceProtocol: GRPCCore.RegistrableRPCService { - /// One empty request followed by one empty response. - func emptyCall( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream - - /// One request followed by one response. - func unaryCall( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream - - /// One request followed by one response. Response has cache control - /// headers set such that a caching HTTP proxy (such as GFE) can - /// satisfy subsequent requests. - func cacheableUnaryCall( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream - - /// One request followed by a sequence of responses (streamed download). - /// The server returns the payload with client desired type and sizes. - func streamingOutputCall( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream - - /// A sequence of requests followed by one response (streamed upload). - /// The server returns the aggregated size of client payload as the result. - func streamingInputCall( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream - - /// 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( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream - - /// A sequence of requests followed by a sequence of responses. - /// 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( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream - - /// The test server will not implement this method. It will be used - /// to test the behavior when clients call unimplemented methods. - func unimplementedCall( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream -} - -/// Conformance to `GRPCCore.RegistrableRPCService`. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension Grpc_Testing_TestService.StreamingServiceProtocol { - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public func registerMethods(with router: inout GRPCCore.RPCRouter) { - router.registerHandler( - forMethod: Grpc_Testing_TestService.Method.EmptyCall.descriptor, - deserializer: GRPCProtobuf.ProtobufDeserializer(), - serializer: GRPCProtobuf.ProtobufSerializer(), - handler: { request, context in - try await self.emptyCall( - request: request, - context: context - ) - } - ) - router.registerHandler( - forMethod: Grpc_Testing_TestService.Method.UnaryCall.descriptor, - deserializer: GRPCProtobuf.ProtobufDeserializer(), - serializer: GRPCProtobuf.ProtobufSerializer(), - handler: { request, context in - try await self.unaryCall( - request: request, - context: context - ) - } - ) - router.registerHandler( - forMethod: Grpc_Testing_TestService.Method.CacheableUnaryCall.descriptor, - deserializer: GRPCProtobuf.ProtobufDeserializer(), - serializer: GRPCProtobuf.ProtobufSerializer(), - handler: { request, context in - try await self.cacheableUnaryCall( - request: request, - context: context - ) - } - ) - router.registerHandler( - forMethod: Grpc_Testing_TestService.Method.StreamingOutputCall.descriptor, - deserializer: GRPCProtobuf.ProtobufDeserializer(), - serializer: GRPCProtobuf.ProtobufSerializer(), - handler: { request, context in - try await self.streamingOutputCall( - request: request, - context: context - ) - } - ) - router.registerHandler( - forMethod: Grpc_Testing_TestService.Method.StreamingInputCall.descriptor, - deserializer: GRPCProtobuf.ProtobufDeserializer(), - serializer: GRPCProtobuf.ProtobufSerializer(), - handler: { request, context in - try await self.streamingInputCall( - request: request, - context: context - ) - } - ) - router.registerHandler( - forMethod: Grpc_Testing_TestService.Method.FullDuplexCall.descriptor, - deserializer: GRPCProtobuf.ProtobufDeserializer(), - serializer: GRPCProtobuf.ProtobufSerializer(), - handler: { request, context in - try await self.fullDuplexCall( - request: request, - context: context - ) - } - ) - router.registerHandler( - forMethod: Grpc_Testing_TestService.Method.HalfDuplexCall.descriptor, - deserializer: GRPCProtobuf.ProtobufDeserializer(), - serializer: GRPCProtobuf.ProtobufSerializer(), - handler: { request, context in - try await self.halfDuplexCall( - request: request, - context: context - ) - } - ) - router.registerHandler( - forMethod: Grpc_Testing_TestService.Method.UnimplementedCall.descriptor, - deserializer: GRPCProtobuf.ProtobufDeserializer(), - serializer: GRPCProtobuf.ProtobufSerializer(), - handler: { request, context in - try await self.unimplementedCall( - request: request, - context: context - ) - } - ) - } -} - -/// A simple service to test the various types of RPCs and experiment with -/// performance with various types of payload. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -public protocol Grpc_Testing_TestServiceServiceProtocol: Grpc_Testing_TestService.StreamingServiceProtocol { - /// One empty request followed by one empty response. - func emptyCall( - request: GRPCCore.ServerRequest.Single, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Single - - /// One request followed by one response. - func unaryCall( - request: GRPCCore.ServerRequest.Single, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Single - - /// One request followed by one response. Response has cache control - /// headers set such that a caching HTTP proxy (such as GFE) can - /// satisfy subsequent requests. - func cacheableUnaryCall( - request: GRPCCore.ServerRequest.Single, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Single - - /// One request followed by a sequence of responses (streamed download). - /// The server returns the payload with client desired type and sizes. - func streamingOutputCall( - request: GRPCCore.ServerRequest.Single, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream - - /// A sequence of requests followed by one response (streamed upload). - /// The server returns the aggregated size of client payload as the result. - func streamingInputCall( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Single - - /// 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( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream - - /// A sequence of requests followed by a sequence of responses. - /// 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( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream - - /// The test server will not implement this method. It will be used - /// to test the behavior when clients call unimplemented methods. - func unimplementedCall( - request: GRPCCore.ServerRequest.Single, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Single -} - -/// Partial conformance to `Grpc_Testing_TestServiceStreamingServiceProtocol`. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension Grpc_Testing_TestService.ServiceProtocol { - public func emptyCall( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream { - let response = try await self.emptyCall( - request: GRPCCore.ServerRequest.Single(stream: request), - context: context - ) - return GRPCCore.ServerResponse.Stream(single: response) - } - - public func unaryCall( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream { - let response = try await self.unaryCall( - request: GRPCCore.ServerRequest.Single(stream: request), - context: context - ) - return GRPCCore.ServerResponse.Stream(single: response) - } - - public func cacheableUnaryCall( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream { - let response = try await self.cacheableUnaryCall( - request: GRPCCore.ServerRequest.Single(stream: request), - context: context - ) - return GRPCCore.ServerResponse.Stream(single: response) - } - - public func streamingOutputCall( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream { - let response = try await self.streamingOutputCall( - request: GRPCCore.ServerRequest.Single(stream: request), - context: context - ) - return response - } - - public func streamingInputCall( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream { - let response = try await self.streamingInputCall( - request: request, - context: context - ) - return GRPCCore.ServerResponse.Stream(single: response) - } - - public func unimplementedCall( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream { - let response = try await self.unimplementedCall( - request: GRPCCore.ServerRequest.Single(stream: request), - context: context - ) - return GRPCCore.ServerResponse.Stream(single: response) - } -} - -/// A simple service NOT implemented at servers so clients can test for -/// that case. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -public protocol Grpc_Testing_UnimplementedServiceStreamingServiceProtocol: GRPCCore.RegistrableRPCService { - /// A call that no server should implement - func unimplementedCall( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream -} - -/// Conformance to `GRPCCore.RegistrableRPCService`. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension Grpc_Testing_UnimplementedService.StreamingServiceProtocol { - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public func registerMethods(with router: inout GRPCCore.RPCRouter) { - router.registerHandler( - forMethod: Grpc_Testing_UnimplementedService.Method.UnimplementedCall.descriptor, - deserializer: GRPCProtobuf.ProtobufDeserializer(), - serializer: GRPCProtobuf.ProtobufSerializer(), - handler: { request, context in - try await self.unimplementedCall( - request: request, - context: context - ) - } - ) - } -} - -/// A simple service NOT implemented at servers so clients can test for -/// that case. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -public protocol Grpc_Testing_UnimplementedServiceServiceProtocol: Grpc_Testing_UnimplementedService.StreamingServiceProtocol { - /// A call that no server should implement - func unimplementedCall( - request: GRPCCore.ServerRequest.Single, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Single -} - -/// Partial conformance to `Grpc_Testing_UnimplementedServiceStreamingServiceProtocol`. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension Grpc_Testing_UnimplementedService.ServiceProtocol { - public func unimplementedCall( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream { - let response = try await self.unimplementedCall( - request: GRPCCore.ServerRequest.Single(stream: request), - context: context - ) - return GRPCCore.ServerResponse.Stream(single: response) - } -} - -/// A service used to control reconnect server. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -public protocol Grpc_Testing_ReconnectServiceStreamingServiceProtocol: GRPCCore.RegistrableRPCService { - func start( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream - - func stop( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream -} - -/// Conformance to `GRPCCore.RegistrableRPCService`. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension Grpc_Testing_ReconnectService.StreamingServiceProtocol { - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public func registerMethods(with router: inout GRPCCore.RPCRouter) { - router.registerHandler( - forMethod: Grpc_Testing_ReconnectService.Method.Start.descriptor, - deserializer: GRPCProtobuf.ProtobufDeserializer(), - serializer: GRPCProtobuf.ProtobufSerializer(), - handler: { request, context in - try await self.start( - request: request, - context: context - ) - } - ) - router.registerHandler( - forMethod: Grpc_Testing_ReconnectService.Method.Stop.descriptor, - deserializer: GRPCProtobuf.ProtobufDeserializer(), - serializer: GRPCProtobuf.ProtobufSerializer(), - handler: { request, context in - try await self.stop( - request: request, - context: context - ) - } - ) - } -} - -/// A service used to control reconnect server. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -public protocol Grpc_Testing_ReconnectServiceServiceProtocol: Grpc_Testing_ReconnectService.StreamingServiceProtocol { - func start( - request: GRPCCore.ServerRequest.Single, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Single - - func stop( - request: GRPCCore.ServerRequest.Single, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Single -} - -/// Partial conformance to `Grpc_Testing_ReconnectServiceStreamingServiceProtocol`. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension Grpc_Testing_ReconnectService.ServiceProtocol { - public func start( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream { - let response = try await self.start( - request: GRPCCore.ServerRequest.Single(stream: request), - context: context - ) - return GRPCCore.ServerResponse.Stream(single: response) - } - - public func stop( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream { - let response = try await self.stop( - request: GRPCCore.ServerRequest.Single(stream: request), - context: context - ) - return GRPCCore.ServerResponse.Stream(single: response) - } -} - -/// A simple service to test the various types of RPCs and experiment with -/// performance with various types of payload. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -public protocol Grpc_Testing_TestServiceClientProtocol: Sendable { - /// One empty request followed by one empty response. - func emptyCall( - request: GRPCCore.ClientRequest.Single, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R - ) async throws -> R where R: Sendable - - /// One request followed by one response. - func unaryCall( - request: GRPCCore.ClientRequest.Single, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R - ) async throws -> R where R: Sendable - - /// One request followed by one response. Response has cache control - /// headers set such that a caching HTTP proxy (such as GFE) can - /// satisfy subsequent requests. - func cacheableUnaryCall( - request: GRPCCore.ClientRequest.Single, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R - ) async throws -> R where R: Sendable - - /// One request followed by a sequence of responses (streamed download). - /// The server returns the payload with client desired type and sizes. - func streamingOutputCall( - request: GRPCCore.ClientRequest.Single, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Stream) async throws -> R - ) async throws -> R where R: Sendable - - /// A sequence of requests followed by one response (streamed upload). - /// The server returns the aggregated size of client payload as the result. - func streamingInputCall( - request: GRPCCore.ClientRequest.Stream, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R - ) async throws -> R where R: Sendable - - /// 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( - request: GRPCCore.ClientRequest.Stream, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Stream) async throws -> R - ) async throws -> R where R: Sendable - - /// A sequence of requests followed by a sequence of responses. - /// 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( - request: GRPCCore.ClientRequest.Stream, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Stream) async throws -> R - ) async throws -> R where R: Sendable - - /// The test server will not implement this method. It will be used - /// to test the behavior when clients call unimplemented methods. - func unimplementedCall( - request: GRPCCore.ClientRequest.Single, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R - ) async throws -> R where R: Sendable -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension Grpc_Testing_TestService.ClientProtocol { - public func emptyCall( - request: GRPCCore.ClientRequest.Single, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R = { - try $0.message - } - ) async throws -> R where R: Sendable { - try await self.emptyCall( - request: request, - serializer: GRPCProtobuf.ProtobufSerializer(), - deserializer: GRPCProtobuf.ProtobufDeserializer(), - options: options, - body - ) - } - - public func unaryCall( - request: GRPCCore.ClientRequest.Single, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R = { - try $0.message - } - ) async throws -> R where R: Sendable { - try await self.unaryCall( - request: request, - serializer: GRPCProtobuf.ProtobufSerializer(), - deserializer: GRPCProtobuf.ProtobufDeserializer(), - options: options, - body - ) - } - - public func cacheableUnaryCall( - request: GRPCCore.ClientRequest.Single, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R = { - try $0.message - } - ) async throws -> R where R: Sendable { - try await self.cacheableUnaryCall( - request: request, - serializer: GRPCProtobuf.ProtobufSerializer(), - deserializer: GRPCProtobuf.ProtobufDeserializer(), - options: options, - body - ) - } - - public func streamingOutputCall( - request: GRPCCore.ClientRequest.Single, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Stream) async throws -> R - ) async throws -> R where R: Sendable { - try await self.streamingOutputCall( - request: request, - serializer: GRPCProtobuf.ProtobufSerializer(), - deserializer: GRPCProtobuf.ProtobufDeserializer(), - options: options, - body - ) - } - - public func streamingInputCall( - request: GRPCCore.ClientRequest.Stream, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R = { - try $0.message - } - ) async throws -> R where R: Sendable { - try await self.streamingInputCall( - request: request, - serializer: GRPCProtobuf.ProtobufSerializer(), - deserializer: GRPCProtobuf.ProtobufDeserializer(), - options: options, - body - ) - } - - public func fullDuplexCall( - request: GRPCCore.ClientRequest.Stream, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Stream) async throws -> R - ) async throws -> R where R: Sendable { - try await self.fullDuplexCall( - request: request, - serializer: GRPCProtobuf.ProtobufSerializer(), - deserializer: GRPCProtobuf.ProtobufDeserializer(), - options: options, - body - ) - } - - public func halfDuplexCall( - request: GRPCCore.ClientRequest.Stream, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Stream) async throws -> R - ) async throws -> R where R: Sendable { - try await self.halfDuplexCall( - request: request, - serializer: GRPCProtobuf.ProtobufSerializer(), - deserializer: GRPCProtobuf.ProtobufDeserializer(), - options: options, - body - ) - } - - public func unimplementedCall( - request: GRPCCore.ClientRequest.Single, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R = { - try $0.message - } - ) async throws -> R where R: Sendable { - try await self.unimplementedCall( - request: request, - serializer: GRPCProtobuf.ProtobufSerializer(), - deserializer: GRPCProtobuf.ProtobufDeserializer(), - options: options, - body - ) - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension Grpc_Testing_TestService.ClientProtocol { - /// One empty request followed by one empty response. - public func emptyCall( - _ message: Grpc_Testing_Empty, - metadata: GRPCCore.Metadata = [:], - options: GRPCCore.CallOptions = .defaults, - onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> Result = { - try $0.message - } - ) async throws -> Result where Result: Sendable { - let request = GRPCCore.ClientRequest.Single( - message: message, - metadata: metadata - ) - return try await self.emptyCall( - request: request, - options: options, - handleResponse - ) - } - - /// One request followed by one response. - public func unaryCall( - _ message: Grpc_Testing_SimpleRequest, - metadata: GRPCCore.Metadata = [:], - options: GRPCCore.CallOptions = .defaults, - onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> Result = { - try $0.message - } - ) async throws -> Result where Result: Sendable { - let request = GRPCCore.ClientRequest.Single( - message: message, - metadata: metadata - ) - return try await self.unaryCall( - request: request, - options: options, - handleResponse - ) - } - - /// One request followed by one response. Response has cache control - /// headers set such that a caching HTTP proxy (such as GFE) can - /// satisfy subsequent requests. - public func cacheableUnaryCall( - _ message: Grpc_Testing_SimpleRequest, - metadata: GRPCCore.Metadata = [:], - options: GRPCCore.CallOptions = .defaults, - onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> Result = { - try $0.message - } - ) async throws -> Result where Result: Sendable { - let request = GRPCCore.ClientRequest.Single( - message: message, - metadata: metadata - ) - return try await self.cacheableUnaryCall( - request: request, - options: options, - handleResponse - ) - } - - /// One request followed by a sequence of responses (streamed download). - /// The server returns the payload with client desired type and sizes. - public func streamingOutputCall( - _ message: Grpc_Testing_StreamingOutputCallRequest, - metadata: GRPCCore.Metadata = [:], - options: GRPCCore.CallOptions = .defaults, - onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse.Stream) async throws -> Result - ) async throws -> Result where Result: Sendable { - let request = GRPCCore.ClientRequest.Single( - message: message, - metadata: metadata - ) - return try await self.streamingOutputCall( - request: request, - options: options, - handleResponse - ) - } - - /// A sequence of requests followed by one response (streamed upload). - /// The server returns the aggregated size of client payload as the result. - public func streamingInputCall( - metadata: GRPCCore.Metadata = [:], - options: GRPCCore.CallOptions = .defaults, - requestProducer: @Sendable @escaping (GRPCCore.RPCWriter) async throws -> Void, - onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> Result = { - try $0.message - } - ) async throws -> Result where Result: Sendable { - let request = GRPCCore.ClientRequest.Stream( - metadata: metadata, - producer: requestProducer - ) - return try await self.streamingInputCall( - request: request, - options: options, - handleResponse - ) - } - - /// 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. - public func fullDuplexCall( - metadata: GRPCCore.Metadata = [:], - options: GRPCCore.CallOptions = .defaults, - requestProducer: @Sendable @escaping (GRPCCore.RPCWriter) async throws -> Void, - onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse.Stream) async throws -> Result - ) async throws -> Result where Result: Sendable { - let request = GRPCCore.ClientRequest.Stream( - metadata: metadata, - producer: requestProducer - ) - return try await self.fullDuplexCall( - request: request, - options: options, - handleResponse - ) - } - - /// A sequence of requests followed by a sequence of responses. - /// 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. - public func halfDuplexCall( - metadata: GRPCCore.Metadata = [:], - options: GRPCCore.CallOptions = .defaults, - requestProducer: @Sendable @escaping (GRPCCore.RPCWriter) async throws -> Void, - onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse.Stream) async throws -> Result - ) async throws -> Result where Result: Sendable { - let request = GRPCCore.ClientRequest.Stream( - metadata: metadata, - producer: requestProducer - ) - return try await self.halfDuplexCall( - request: request, - options: options, - handleResponse - ) - } - - /// The test server will not implement this method. It will be used - /// to test the behavior when clients call unimplemented methods. - public func unimplementedCall( - _ message: Grpc_Testing_Empty, - metadata: GRPCCore.Metadata = [:], - options: GRPCCore.CallOptions = .defaults, - onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> Result = { - try $0.message - } - ) async throws -> Result where Result: Sendable { - let request = GRPCCore.ClientRequest.Single( - message: message, - metadata: metadata - ) - return try await self.unimplementedCall( - request: request, - options: options, - handleResponse - ) - } -} - -/// A simple service to test the various types of RPCs and experiment with -/// performance with various types of payload. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -public struct Grpc_Testing_TestServiceClient: Grpc_Testing_TestService.ClientProtocol { - private let client: GRPCCore.GRPCClient - - public init(wrapping client: GRPCCore.GRPCClient) { - self.client = client - } - - /// One empty request followed by one empty response. - public func emptyCall( - request: GRPCCore.ClientRequest.Single, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R = { - try $0.message - } - ) async throws -> R where R: Sendable { - try await self.client.unary( - request: request, - descriptor: Grpc_Testing_TestService.Method.EmptyCall.descriptor, - serializer: serializer, - deserializer: deserializer, - options: options, - handler: body - ) - } - - /// One request followed by one response. - public func unaryCall( - request: GRPCCore.ClientRequest.Single, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R = { - try $0.message - } - ) async throws -> R where R: Sendable { - try await self.client.unary( - request: request, - descriptor: Grpc_Testing_TestService.Method.UnaryCall.descriptor, - serializer: serializer, - deserializer: deserializer, - options: options, - handler: body - ) - } - - /// One request followed by one response. Response has cache control - /// headers set such that a caching HTTP proxy (such as GFE) can - /// satisfy subsequent requests. - public func cacheableUnaryCall( - request: GRPCCore.ClientRequest.Single, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R = { - try $0.message - } - ) async throws -> R where R: Sendable { - try await self.client.unary( - request: request, - descriptor: Grpc_Testing_TestService.Method.CacheableUnaryCall.descriptor, - serializer: serializer, - deserializer: deserializer, - options: options, - handler: body - ) - } - - /// One request followed by a sequence of responses (streamed download). - /// The server returns the payload with client desired type and sizes. - public func streamingOutputCall( - request: GRPCCore.ClientRequest.Single, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Stream) async throws -> R - ) async throws -> R where R: Sendable { - try await self.client.serverStreaming( - request: request, - descriptor: Grpc_Testing_TestService.Method.StreamingOutputCall.descriptor, - serializer: serializer, - deserializer: deserializer, - options: options, - handler: body - ) - } - - /// A sequence of requests followed by one response (streamed upload). - /// The server returns the aggregated size of client payload as the result. - public func streamingInputCall( - request: GRPCCore.ClientRequest.Stream, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R = { - try $0.message - } - ) async throws -> R where R: Sendable { - try await self.client.clientStreaming( - request: request, - descriptor: Grpc_Testing_TestService.Method.StreamingInputCall.descriptor, - serializer: serializer, - deserializer: deserializer, - options: options, - handler: body - ) - } - - /// 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. - public func fullDuplexCall( - request: GRPCCore.ClientRequest.Stream, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Stream) async throws -> R - ) async throws -> R where R: Sendable { - try await self.client.bidirectionalStreaming( - request: request, - descriptor: Grpc_Testing_TestService.Method.FullDuplexCall.descriptor, - serializer: serializer, - deserializer: deserializer, - options: options, - handler: body - ) - } - - /// A sequence of requests followed by a sequence of responses. - /// 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. - public func halfDuplexCall( - request: GRPCCore.ClientRequest.Stream, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Stream) async throws -> R - ) async throws -> R where R: Sendable { - try await self.client.bidirectionalStreaming( - request: request, - descriptor: Grpc_Testing_TestService.Method.HalfDuplexCall.descriptor, - serializer: serializer, - deserializer: deserializer, - options: options, - handler: body - ) - } - - /// The test server will not implement this method. It will be used - /// to test the behavior when clients call unimplemented methods. - public func unimplementedCall( - request: GRPCCore.ClientRequest.Single, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R = { - try $0.message - } - ) async throws -> R where R: Sendable { - try await self.client.unary( - request: request, - descriptor: Grpc_Testing_TestService.Method.UnimplementedCall.descriptor, - serializer: serializer, - deserializer: deserializer, - options: options, - handler: body - ) - } -} - -/// A simple service NOT implemented at servers so clients can test for -/// that case. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -public protocol Grpc_Testing_UnimplementedServiceClientProtocol: Sendable { - /// A call that no server should implement - func unimplementedCall( - request: GRPCCore.ClientRequest.Single, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R - ) async throws -> R where R: Sendable -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension Grpc_Testing_UnimplementedService.ClientProtocol { - public func unimplementedCall( - request: GRPCCore.ClientRequest.Single, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R = { - try $0.message - } - ) async throws -> R where R: Sendable { - try await self.unimplementedCall( - request: request, - serializer: GRPCProtobuf.ProtobufSerializer(), - deserializer: GRPCProtobuf.ProtobufDeserializer(), - options: options, - body - ) - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension Grpc_Testing_UnimplementedService.ClientProtocol { - /// A call that no server should implement - public func unimplementedCall( - _ message: Grpc_Testing_Empty, - metadata: GRPCCore.Metadata = [:], - options: GRPCCore.CallOptions = .defaults, - onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> Result = { - try $0.message - } - ) async throws -> Result where Result: Sendable { - let request = GRPCCore.ClientRequest.Single( - message: message, - metadata: metadata - ) - return try await self.unimplementedCall( - request: request, - options: options, - handleResponse - ) - } -} - -/// A simple service NOT implemented at servers so clients can test for -/// that case. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -public struct Grpc_Testing_UnimplementedServiceClient: Grpc_Testing_UnimplementedService.ClientProtocol { - private let client: GRPCCore.GRPCClient - - public init(wrapping client: GRPCCore.GRPCClient) { - self.client = client - } - - /// A call that no server should implement - public func unimplementedCall( - request: GRPCCore.ClientRequest.Single, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R = { - try $0.message - } - ) async throws -> R where R: Sendable { - try await self.client.unary( - request: request, - descriptor: Grpc_Testing_UnimplementedService.Method.UnimplementedCall.descriptor, - serializer: serializer, - deserializer: deserializer, - options: options, - handler: body - ) - } -} - -/// A service used to control reconnect server. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -public protocol Grpc_Testing_ReconnectServiceClientProtocol: Sendable { - func start( - request: GRPCCore.ClientRequest.Single, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R - ) async throws -> R where R: Sendable - - func stop( - request: GRPCCore.ClientRequest.Single, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R - ) async throws -> R where R: Sendable -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension Grpc_Testing_ReconnectService.ClientProtocol { - public func start( - request: GRPCCore.ClientRequest.Single, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R = { - try $0.message - } - ) async throws -> R where R: Sendable { - try await self.start( - request: request, - serializer: GRPCProtobuf.ProtobufSerializer(), - deserializer: GRPCProtobuf.ProtobufDeserializer(), - options: options, - body - ) - } - - public func stop( - request: GRPCCore.ClientRequest.Single, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R = { - try $0.message - } - ) async throws -> R where R: Sendable { - try await self.stop( - request: request, - serializer: GRPCProtobuf.ProtobufSerializer(), - deserializer: GRPCProtobuf.ProtobufDeserializer(), - options: options, - body - ) - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension Grpc_Testing_ReconnectService.ClientProtocol { - public func start( - _ message: Grpc_Testing_ReconnectParams, - metadata: GRPCCore.Metadata = [:], - options: GRPCCore.CallOptions = .defaults, - onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> Result = { - try $0.message - } - ) async throws -> Result where Result: Sendable { - let request = GRPCCore.ClientRequest.Single( - message: message, - metadata: metadata - ) - return try await self.start( - request: request, - options: options, - handleResponse - ) - } - - public func stop( - _ message: Grpc_Testing_Empty, - metadata: GRPCCore.Metadata = [:], - options: GRPCCore.CallOptions = .defaults, - onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> Result = { - try $0.message - } - ) async throws -> Result where Result: Sendable { - let request = GRPCCore.ClientRequest.Single( - message: message, - metadata: metadata - ) - return try await self.stop( - request: request, - options: options, - handleResponse - ) - } -} - -/// A service used to control reconnect server. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -public struct Grpc_Testing_ReconnectServiceClient: Grpc_Testing_ReconnectService.ClientProtocol { - private let client: GRPCCore.GRPCClient - - public init(wrapping client: GRPCCore.GRPCClient) { - self.client = client - } - - public func start( - request: GRPCCore.ClientRequest.Single, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R = { - try $0.message - } - ) async throws -> R where R: Sendable { - try await self.client.unary( - request: request, - descriptor: Grpc_Testing_ReconnectService.Method.Start.descriptor, - serializer: serializer, - deserializer: deserializer, - options: options, - handler: body - ) - } - - public func stop( - request: GRPCCore.ClientRequest.Single, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R = { - try $0.message - } - ) async throws -> R where R: Sendable { - try await self.client.unary( - request: request, - descriptor: Grpc_Testing_ReconnectService.Method.Stop.descriptor, - serializer: serializer, - deserializer: deserializer, - options: options, - handler: body - ) - } -} \ No newline at end of file diff --git a/Sources/InteroperabilityTests/Generated/test.pb.swift b/Sources/InteroperabilityTests/Generated/test.pb.swift deleted file mode 100644 index 8947a84cb..000000000 --- a/Sources/InteroperabilityTests/Generated/test.pb.swift +++ /dev/null @@ -1,28 +0,0 @@ -// DO NOT EDIT. -// swift-format-ignore-file -// swiftlint:disable all -// -// Generated by the Swift generator plugin for the protocol buffer compiler. -// Source: src/proto/grpc/testing/test.proto -// -// For information on using the generated types, please see the documentation: -// https://github.com/apple/swift-protobuf/ - -// Copyright 2015-2016 gRPC authors. -// -// 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. - -// An integration test service that covers all the method signature permutations -// of unary/streaming requests/responses. - -// This file contained no messages, enums, or extensions. diff --git a/Sources/InteroperabilityTests/InteroperabilityTestCase.swift b/Sources/InteroperabilityTests/InteroperabilityTestCase.swift deleted file mode 100644 index 1c60f1401..000000000 --- a/Sources/InteroperabilityTests/InteroperabilityTestCase.swift +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ -public import GRPCCore - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -public protocol InteroperabilityTest { - /// Run a test case using the given connection. - /// - /// The test case is considered unsuccessful if any exception is thrown, conversely if no - /// exceptions are thrown it is successful. - /// - /// - Parameter client: The client to use for the test. - /// - Throws: Any exception may be thrown to indicate an unsuccessful test. - func run(client: GRPCClient) async throws -} - -/// Test cases as listed by the [gRPC interoperability test description specification] -/// (https://github.com/grpc/grpc/blob/master/doc/interop-test-descriptions.md). -/// -/// This is not a complete list, the following tests have not been implemented: -/// - cacheable_unary (caching not supported) -/// - cancel_after_begin (if the client cancels the task running the request, there's no response to be -/// received, so we can't check we got back a Cancelled status code) -/// - cancel_after_first_response (same reason as above) -/// - client_compressed_streaming (we don't support per-message compression, so we can't implement this) -/// - compute_engine_creds -/// - jwt_token_creds -/// - oauth2_auth_token -/// - per_rpc_creds -/// - google_default_credentials -/// - compute_engine_channel_credentials -/// - timeout_on_sleeping_server (timeouts end up being surfaced as `CancellationError`s, so we -/// can't really implement this test) -/// -/// Note: Tests for compression have not been implemented yet as compression is -/// not supported. Once the API which allows for compression will be implemented -/// these tests should be added. -public enum InteroperabilityTestCase: String, CaseIterable, Sendable { - case emptyUnary = "empty_unary" - case largeUnary = "large_unary" - case clientCompressedUnary = "client_compressed_unary" - case serverCompressedUnary = "server_compressed_unary" - case clientStreaming = "client_streaming" - case serverStreaming = "server_streaming" - case serverCompressedStreaming = "server_compressed_streaming" - case pingPong = "ping_pong" - case emptyStream = "empty_stream" - case customMetadata = "custom_metadata" - case statusCodeAndMessage = "status_code_and_message" - case specialStatusMessage = "special_status_message" - case unimplementedMethod = "unimplemented_method" - case unimplementedService = "unimplemented_service" - - public var name: String { - return self.rawValue - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension InteroperabilityTestCase { - /// Return a new instance of the test case. - public func makeTest() -> any InteroperabilityTest { - switch self { - case .emptyUnary: - return EmptyUnary() - case .largeUnary: - return LargeUnary() - case .clientCompressedUnary: - return ClientCompressedUnary() - case .serverCompressedUnary: - return ServerCompressedUnary() - case .clientStreaming: - return ClientStreaming() - case .serverStreaming: - return ServerStreaming() - case .serverCompressedStreaming: - return ServerCompressedStreaming() - case .pingPong: - return PingPong() - case .emptyStream: - return EmptyStream() - case .customMetadata: - return CustomMetadata() - case .statusCodeAndMessage: - return StatusCodeAndMessage() - case .specialStatusMessage: - return SpecialStatusMessage() - case .unimplementedMethod: - return UnimplementedMethod() - case .unimplementedService: - return UnimplementedService() - } - } -} diff --git a/Sources/InteroperabilityTests/InteroperabilityTestCases.swift b/Sources/InteroperabilityTests/InteroperabilityTestCases.swift deleted file mode 100644 index b1be0be50..000000000 --- a/Sources/InteroperabilityTests/InteroperabilityTestCases.swift +++ /dev/null @@ -1,996 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -internal import GRPCCore - -private import struct Foundation.Data - -/// This test verifies that implementations support zero-size messages. Ideally, client -/// implementations would verify that the request and response were zero bytes serialized, but -/// this is generally prohibitive to perform, so is not required. -/// -/// Server features: -/// - EmptyCall -/// -/// Procedure: -/// 1. Client calls EmptyCall with the default Empty message -/// -/// Client asserts: -/// - call was successful -/// - response is non-null -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -struct EmptyUnary: InteroperabilityTest { - func run(client: GRPCClient) async throws { - let testServiceClient = Grpc_Testing_TestService.Client(wrapping: client) - try await testServiceClient.emptyCall( - request: ClientRequest.Single(message: Grpc_Testing_Empty()) - ) { response in - try assertEqual(response.message, Grpc_Testing_Empty()) - } - } -} - -/// This test verifies unary calls succeed in sending messages, and touches on flow control (even -/// if compression is enabled on the channel). -/// -/// Server features: -/// - UnaryCall -/// -/// Procedure: -/// 1. Client calls UnaryCall with: -/// ``` -/// { -/// response_size: 314159 -/// payload:{ -/// body: 271828 bytes of zeros -/// } -/// } -/// ``` -/// -/// Client asserts: -/// - call was successful -/// - response payload body is 314159 bytes in size -/// - clients are free to assert that the response payload body contents are zero and comparing -/// the entire response message against a golden response -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -struct LargeUnary: InteroperabilityTest { - func run(client: GRPCClient) async throws { - let testServiceClient = Grpc_Testing_TestService.Client(wrapping: client) - let request = Grpc_Testing_SimpleRequest.with { request in - request.responseSize = 314_159 - request.payload = Grpc_Testing_Payload.with { - $0.body = Data(count: 271_828) - } - } - try await testServiceClient.unaryCall( - request: ClientRequest.Single(message: request) - ) { response in - try assertEqual( - response.message.payload, - Grpc_Testing_Payload.with { - $0.body = Data(count: 314_159) - } - ) - } - } -} - -/// This test verifies the client can compress unary messages by sending two unary calls, for -/// compressed and uncompressed payloads. It also sends an initial probing request to verify -/// whether the server supports the CompressedRequest feature by checking if the probing call -/// fails with an `INVALID_ARGUMENT` status. -/// -/// Server features: -/// - UnaryCall -/// - CompressedRequest -/// -/// Procedure: -/// 1. Client calls UnaryCall with the feature probe, an *uncompressed* message: -/// ``` -/// { -/// expect_compressed:{ -/// value: true -/// } -/// response_size: 314159 -/// payload:{ -/// body: 271828 bytes of zeros -/// } -/// } -/// ``` -/// 2. Client calls UnaryCall with the *compressed* message: -/// ``` -/// { -/// expect_compressed:{ -/// value: true -/// } -/// response_size: 314159 -/// payload:{ -/// body: 271828 bytes of zeros -/// } -/// } -/// ``` -/// 3. Client calls UnaryCall with the *uncompressed* message: -/// ``` -/// { -/// expect_compressed:{ -/// value: false -/// } -/// response_size: 314159 -/// payload:{ -/// body: 271828 bytes of zeros -/// } -/// } -/// ``` -/// -/// Client asserts: -/// - First call failed with `INVALID_ARGUMENT` status. -/// - Subsequent calls were successful. -/// - Response payload body is 314159 bytes in size. -/// - Clients are free to assert that the response payload body contents are zeros and comparing the -/// entire response message against a golden response. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -class ClientCompressedUnary: InteroperabilityTest { - func run(client: GRPCClient) async throws { - let testServiceClient = Grpc_Testing_TestService.Client(wrapping: client) - let compressedRequest = Grpc_Testing_SimpleRequest.with { request in - request.expectCompressed = .with { $0.value = true } - request.responseSize = 314_159 - request.payload = .with { $0.body = Data(repeating: 0, count: 271_828) } - } - - var uncompressedRequest = compressedRequest - uncompressedRequest.expectCompressed = .with { $0.value = false } - - // For unary RPCs we disable compression at the call level. - var options = CallOptions.defaults - - // With compression expected but *disabled*. - options.compression = CompressionAlgorithm.none - try await testServiceClient.unaryCall( - request: ClientRequest.Single(message: compressedRequest), - options: options - ) { response in - switch response.accepted { - case .success: - throw AssertionFailure(message: "The result should be an error.") - case .failure(let error): - try assertEqual(error.code, .invalidArgument) - } - } - - // With compression expected and enabled. - options.compression = .gzip - - try await testServiceClient.unaryCall( - request: ClientRequest.Single(message: compressedRequest), - options: options - ) { response in - switch response.accepted { - case .success(let success): - try assertEqual(success.message.get().payload.body, Data(repeating: 0, count: 314_159)) - case .failure: - throw AssertionFailure(message: "Response should have been accepted.") - } - } - - // With compression not expected and disabled. - options.compression = CompressionAlgorithm.none - try await testServiceClient.unaryCall( - request: ClientRequest.Single(message: uncompressedRequest), - options: options - ) { response in - switch response.accepted { - case .success(let success): - try assertEqual(success.message.get().payload.body, Data(repeating: 0, count: 314_159)) - case .failure: - throw AssertionFailure(message: "Response should have been accepted.") - } - } - } -} - -/// This test verifies the server can compress unary messages. It sends two unary -/// requests, expecting the server's response to be compressed or not according to -/// the `response_compressed` boolean. -/// -/// Whether compression was actually performed is determined by the compression bit -/// in the response's message flags. *Note that some languages may not have access -/// to the message flags, in which case the client will be unable to verify that -/// the `response_compressed` boolean is obeyed by the server*. -/// -/// -/// Server features: -/// - UnaryCall -/// - CompressedResponse -/// -/// Procedure: -/// 1. Client calls UnaryCall with `SimpleRequest`: -/// ``` -/// { -/// response_compressed:{ -/// value: true -/// } -/// response_size: 314159 -/// payload:{ -/// body: 271828 bytes of zeros -/// } -/// } -/// ``` -/// ``` -/// { -/// response_compressed:{ -/// value: false -/// } -/// response_size: 314159 -/// payload:{ -/// body: 271828 bytes of zeros -/// } -/// } -/// ``` -/// -/// Client asserts: -/// - call was successful -/// - if supported by the implementation, when `response_compressed` is true, the response MUST have -/// the compressed message flag set. -/// - if supported by the implementation, when `response_compressed` is false, the response MUST NOT -/// have the compressed message flag set. -/// - response payload body is 314159 bytes in size in both cases. -/// - clients are free to assert that the response payload body contents are zero and comparing the -/// entire response message against a golden response -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -class ServerCompressedUnary: InteroperabilityTest { - func run(client: GRPCClient) async throws { - let testServiceClient = Grpc_Testing_TestService.Client(wrapping: client) - - let compressedRequest = Grpc_Testing_SimpleRequest.with { request in - request.responseCompressed = .with { $0.value = true } - request.responseSize = 314_159 - request.payload = .with { $0.body = Data(repeating: 0, count: 271_828) } - } - - try await testServiceClient.unaryCall( - request: ClientRequest.Single(message: compressedRequest) - ) { response in - // We can't verify that the compression bit was set, instead we verify that the encoding header - // was sent by the server. This isn't quite the same since as it can still be set but the - // compression may _not_ be set. - try assertTrue(response.metadata["grpc-encoding"].contains { $0 != "identity" }) - - switch response.accepted { - case .success(let success): - try assertEqual(success.message.get().payload.body, Data(repeating: 0, count: 314_159)) - case .failure: - throw AssertionFailure(message: "Response should have been accepted.") - } - } - - var uncompressedRequest = compressedRequest - uncompressedRequest.responseCompressed.value = false - try await testServiceClient.unaryCall( - request: ClientRequest.Single(message: compressedRequest) - ) { response in - // We can't even check for the 'grpc-encoding' header here since it could be set with the - // compression bit on the message not set. - switch response.accepted { - case .success(let success): - try assertEqual(success.message.get().payload.body, Data(repeating: 0, count: 314_159)) - case .failure: - throw AssertionFailure( - message: "Response should have been accepted." - ) - } - } - } -} - -/// This test verifies that client-only streaming succeeds. -/// -/// Server features: -/// - StreamingInputCall -/// -/// Procedure: -/// 1. Client calls StreamingInputCall -/// 2. Client sends: -/// ``` -/// { -/// payload:{ -/// body: 27182 bytes of zeros -/// } -/// } -/// ``` -/// 3. Client then sends: -/// ``` -/// { -/// payload:{ -/// body: 8 bytes of zeros -/// } -/// } -/// ``` -/// 4. Client then sends: -/// ``` -/// { -/// payload:{ -/// body: 1828 bytes of zeros -/// } -/// } -/// ``` -/// 5. Client then sends: -/// ``` -/// { -/// payload:{ -/// body: 45904 bytes of zeros -/// } -/// } -/// ``` -/// 6. Client half-closes -/// -/// Client asserts: -/// - call was successful -/// - response aggregated_payload_size is 74922 -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -struct ClientStreaming: InteroperabilityTest { - func run(client: GRPCClient) async throws { - let testServiceClient = Grpc_Testing_TestService.Client(wrapping: client) - let request = ClientRequest.Stream { writer in - for bytes in [27182, 8, 1828, 45904] { - let message = Grpc_Testing_StreamingInputCallRequest.with { - $0.payload = Grpc_Testing_Payload.with { - $0.body = Data(count: bytes) - } - } - try await writer.write(message) - } - } - - try await testServiceClient.streamingInputCall(request: request) { response in - try assertEqual(response.message.aggregatedPayloadSize, 74922) - } - } -} - -/// This test verifies that server-only streaming succeeds. -/// -/// Server features: -/// - StreamingOutputCall -/// -/// Procedure: -/// 1. Client calls StreamingOutputCall with StreamingOutputCallRequest: -/// ``` -/// { -/// response_parameters:{ -/// size: 31415 -/// } -/// response_parameters:{ -/// size: 9 -/// } -/// response_parameters:{ -/// size: 2653 -/// } -/// response_parameters:{ -/// size: 58979 -/// } -/// } -/// ``` -/// -/// Client asserts: -/// - call was successful -/// - exactly four responses -/// - response payload bodies are sized (in order): 31415, 9, 2653, 58979 -/// - clients are free to assert that the response payload body contents are zero and -/// comparing the entire response messages against golden responses -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -struct ServerStreaming: InteroperabilityTest { - func run(client: GRPCClient) async throws { - let testServiceClient = Grpc_Testing_TestService.Client(wrapping: client) - let responseSizes = [31415, 9, 2653, 58979] - let request = Grpc_Testing_StreamingOutputCallRequest.with { request in - request.responseParameters = responseSizes.map { - var parameter = Grpc_Testing_ResponseParameters() - parameter.size = Int32($0) - return parameter - } - } - - try await testServiceClient.streamingOutputCall( - request: ClientRequest.Single(message: request) - ) { response in - var responseParts = response.messages.makeAsyncIterator() - // There are 4 response sizes, so if there isn't a message for each one, - // it means that the client didn't receive 4 messages back. - for responseSize in responseSizes { - if let message = try await responseParts.next() { - try assertEqual(message.payload.body.count, responseSize) - } else { - throw AssertionFailure( - message: "There were less than four responses received." - ) - } - } - // Check that there were not more than 4 responses from the server. - try assertEqual(try await responseParts.next(), nil) - } - } -} - -/// This test verifies that the server can compress streaming messages and disable compression on -/// individual messages, expecting the server's response to be compressed or not according to the -/// `response_compressed` boolean. -/// -/// Whether compression was actually performed is determined by the compression bit in the -/// response's message flags. *Note that some languages may not have access to the message flags, in -/// which case the client will be unable to verify that the `response_compressed` boolean is obeyed -/// by the server*. -/// -/// Server features: -/// - StreamingOutputCall -/// - CompressedResponse -/// -/// Procedure: -/// 1. Client calls StreamingOutputCall with `StreamingOutputCallRequest`: -/// ``` -/// { -/// response_parameters:{ -/// compressed: { -/// value: true -/// } -/// size: 31415 -/// } -/// response_parameters:{ -/// compressed: { -/// value: false -/// } -/// size: 92653 -/// } -/// } -/// ``` -/// -/// Client asserts: -/// - call was successful -/// - exactly two responses -/// - if supported by the implementation, when `response_compressed` is false, the response's -/// messages MUST NOT have the compressed message flag set. -/// - if supported by the implementation, when `response_compressed` is true, the response's -/// messages MUST have the compressed message flag set. -/// - response payload bodies are sized (in order): 31415, 92653 -/// - clients are free to assert that the response payload body contents are zero and comparing the -/// entire response messages against golden responses -class ServerCompressedStreaming: InteroperabilityTest { - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - func run(client: GRPCClient) async throws { - let testServiceClient = Grpc_Testing_TestService.Client(wrapping: client) - let request: Grpc_Testing_StreamingOutputCallRequest = .with { request in - request.responseParameters = [ - .with { - $0.compressed = .with { $0.value = true } - $0.size = 31415 - }, - .with { - $0.compressed = .with { $0.value = false } - $0.size = 92653 - }, - ] - } - let responseSizes = [31415, 92653] - - try await testServiceClient.streamingOutputCall( - request: ClientRequest.Single(message: request) - ) { response in - var payloads = [Grpc_Testing_Payload]() - - switch response.accepted { - case .success(let success): - // We can't verify that the compression bit was set, instead we verify that the encoding header - // was sent by the server. This isn't quite the same since as it can still be set but the - // compression may be not set. - try assertTrue(success.metadata["grpc-encoding"].contains { $0 != "identity" }) - - for try await part in success.bodyParts { - switch part { - case .message(let message): - payloads.append(message.payload) - case .trailingMetadata: - () - } - } - - case .failure: - throw AssertionFailure(message: "Response should have been accepted.") - } - - try assertEqual( - payloads, - responseSizes.map { size in - Grpc_Testing_Payload.with { - $0.body = Data(repeating: 0, count: size) - } - } - ) - } - } -} - -/// This test verifies that full duplex bidi is supported. -/// -/// Server features: -/// - FullDuplexCall -/// -/// Procedure: -/// 1. Client calls FullDuplexCall with: -/// ``` -/// { -/// response_parameters:{ -/// size: 31415 -/// } -/// payload:{ -/// body: 27182 bytes of zeros -/// } -/// } -/// ``` -/// 2. After getting a reply, it sends: -/// ``` -/// { -/// response_parameters:{ -/// size: 9 -/// } -/// payload:{ -/// body: 8 bytes of zeros -/// } -/// } -/// ``` -/// 3. After getting a reply, it sends: -/// ``` -/// { -/// response_parameters:{ -/// size: 2653 -/// } -/// payload:{ -/// body: 1828 bytes of zeros -/// } -/// } -/// ``` -/// 4. After getting a reply, it sends: -/// ``` -/// { -/// response_parameters:{ -/// size: 58979 -/// } -/// payload:{ -/// body: 45904 bytes of zeros -/// } -/// } -/// ``` -/// 5. After getting a reply, client half-closes -/// -/// Client asserts: -/// - call was successful -/// - exactly four responses -/// - response payload bodies are sized (in order): 31415, 9, 2653, 58979 -/// - clients are free to assert that the response payload body contents are zero and -/// comparing the entire response messages against golden responses -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -struct PingPong: InteroperabilityTest { - func run(client: GRPCClient) async throws { - let testServiceClient = Grpc_Testing_TestService.Client(wrapping: client) - let ids = AsyncStream.makeStream(of: Int.self) - - let request = ClientRequest.Stream { writer in - let sizes = [(31_415, 27_182), (9, 8), (2_653, 1_828), (58_979, 45_904)] - for try await id in ids.stream { - var message = Grpc_Testing_StreamingOutputCallRequest() - switch id { - case 1 ... 4: - let (responseSize, bodySize) = sizes[id - 1] - message.responseParameters = [ - Grpc_Testing_ResponseParameters.with { - $0.size = Int32(responseSize) - } - ] - message.payload = Grpc_Testing_Payload.with { - $0.body = Data(count: bodySize) - } - default: - // When the id is higher than 4 it means the client received all the expected responses - // and it doesn't need to send another message. - return - } - try await writer.write(message) - } - } - ids.continuation.yield(1) - try await testServiceClient.fullDuplexCall(request: request) { response in - var id = 1 - for try await message in response.messages { - switch id { - case 1: - try assertEqual(message.payload.body, Data(count: 31_415)) - case 2: - try assertEqual(message.payload.body, Data(count: 9)) - case 3: - try assertEqual(message.payload.body, Data(count: 2_653)) - case 4: - try assertEqual(message.payload.body, Data(count: 58_979)) - default: - throw AssertionFailure( - message: "We should only receive messages with ids between 1 and 4." - ) - } - - // Add the next id to the continuation. - id += 1 - ids.continuation.yield(id) - } - } - } -} - -/// This test verifies that streams support having zero-messages in both directions. -/// -/// Server features: -/// - FullDuplexCall -/// -/// Procedure: -/// 1. Client calls FullDuplexCall and then half-closes -/// -/// Client asserts: -/// - call was successful -/// - exactly zero responses -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -struct EmptyStream: InteroperabilityTest { - func run(client: GRPCClient) async throws { - let testServiceClient = Grpc_Testing_TestService.Client(wrapping: client) - let request = ClientRequest.Stream { _ in } - - try await testServiceClient.fullDuplexCall(request: request) { response in - var messages = response.messages.makeAsyncIterator() - try await assertEqual(messages.next(), nil) - } - } -} - -/// This test verifies that custom metadata in either binary or ascii format can be sent as -/// initial-metadata by the client and as both initial- and trailing-metadata by the server. -/// -/// Server features: -/// - UnaryCall -/// - FullDuplexCall -/// - Echo Metadata -/// -/// Procedure: -/// 1. The client attaches custom metadata with the following keys and values -/// to a UnaryCall with request: -/// - key: "x-grpc-test-echo-initial", value: "test_initial_metadata_value" -/// - key: "x-grpc-test-echo-trailing-bin", value: 0xababab -/// ``` -/// { -/// response_size: 314159 -/// payload:{ -/// body: 271828 bytes of zeros -/// } -/// } -/// ``` -/// 2. The client attaches custom metadata with the following keys and values -/// to a FullDuplexCall with request: -/// - key: "x-grpc-test-echo-initial", value: "test_initial_metadata_value" -/// - key: "x-grpc-test-echo-trailing-bin", value: 0xababab -/// ``` -/// { -/// response_parameters:{ -/// size: 314159 -/// } -/// payload:{ -/// body: 271828 bytes of zeros -/// } -/// } -/// ``` -/// and then half-closes -/// -/// Client asserts: -/// - call was successful -/// - metadata with key "x-grpc-test-echo-initial" and value "test_initial_metadata_value" is -/// received in the initial metadata for calls in Procedure steps 1 and 2. -/// - metadata with key "x-grpc-test-echo-trailing-bin" and value 0xababab is received in the -/// trailing metadata for calls in Procedure steps 1 and 2. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -struct CustomMetadata: InteroperabilityTest { - let initialMetadataName = "x-grpc-test-echo-initial" - let initialMetadataValue = "test_initial_metadata_value" - - let trailingMetadataName = "x-grpc-test-echo-trailing-bin" - let trailingMetadataValue: [UInt8] = [0xAB, 0xAB, 0xAB] - - func checkInitialMetadata(_ metadata: Metadata) throws { - let values = metadata[self.initialMetadataName] - try assertEqual(Array(values), [.string(self.initialMetadataValue)]) - } - - func checkTrailingMetadata(_ metadata: Metadata) throws { - let values = metadata[self.trailingMetadataName] - try assertEqual(Array(values), [.binary(self.trailingMetadataValue)]) - } - - func run(client: GRPCClient) async throws { - let testServiceClient = Grpc_Testing_TestService.Client(wrapping: client) - - let unaryRequest = Grpc_Testing_SimpleRequest.with { request in - request.responseSize = 314_159 - request.payload = Grpc_Testing_Payload.with { - $0.body = Data(count: 271_828) - } - } - let metadata: Metadata = [ - self.initialMetadataName: .string(self.initialMetadataValue), - self.trailingMetadataName: .binary(self.trailingMetadataValue), - ] - - try await testServiceClient.unaryCall( - request: ClientRequest.Single(message: unaryRequest, metadata: metadata) - ) { response in - // Check the initial metadata. - let receivedInitialMetadata = response.metadata - try checkInitialMetadata(receivedInitialMetadata) - - // Check the message. - try assertEqual(response.message.payload.body, Data(count: 314_159)) - - // Check the trailing metadata. - try checkTrailingMetadata(response.trailingMetadata) - } - - let streamingRequest = ClientRequest.Stream(metadata: metadata) { writer in - let message = Grpc_Testing_StreamingOutputCallRequest.with { - $0.responseParameters = [ - Grpc_Testing_ResponseParameters.with { - $0.size = 314_159 - } - ] - $0.payload = Grpc_Testing_Payload.with { - $0.body = Data(count: 271_828) - } - } - try await writer.write(message) - } - - try await testServiceClient.fullDuplexCall(request: streamingRequest) { response in - switch response.accepted { - case .success(let contents): - // Check the initial metadata. - let receivedInitialMetadata = response.metadata - try self.checkInitialMetadata(receivedInitialMetadata) - - let parts = try await contents.bodyParts.reduce(into: []) { $0.append($1) } - try assertEqual(parts.count, 2) - - for part in parts { - switch part { - // Check the message. - case .message(let message): - try assertEqual(message.payload.body, Data(count: 314_159)) - // Check the trailing metadata. - case .trailingMetadata(let receivedTrailingMetadata): - try self.checkTrailingMetadata(receivedTrailingMetadata) - } - } - case .failure: - throw AssertionFailure( - message: "The client should have received a response from the server." - ) - } - } - } -} - -/// This test verifies unary calls succeed in sending messages, and propagate back status code and -/// message sent along with the messages. -/// -/// Server features: -/// - UnaryCall -/// - FullDuplexCall -/// - Echo Status -/// -/// Procedure: -/// 1. Client calls UnaryCall with: -/// ``` -/// { -/// response_status:{ -/// code: 2 -/// message: "test status message" -/// } -/// } -/// ``` -/// 2. Client calls FullDuplexCall with: -/// ``` -/// { -/// response_status:{ -/// code: 2 -/// message: "test status message" -/// } -/// } -/// ``` -/// 3. and then half-closes -/// -/// Client asserts: -/// - received status code is the same as the sent code for both Procedure steps 1 and 2 -/// - received status message is the same as the sent message for both Procedure steps 1 and 2 -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -struct StatusCodeAndMessage: InteroperabilityTest { - let expectedCode = 2 - let expectedMessage = "test status message" - - func run(client: GRPCClient) async throws { - let testServiceClient = Grpc_Testing_TestService.Client(wrapping: client) - - let message = Grpc_Testing_SimpleRequest.with { - $0.responseStatus = Grpc_Testing_EchoStatus.with { - $0.code = Int32(self.expectedCode) - $0.message = self.expectedMessage - } - } - - try await testServiceClient.unaryCall( - request: ClientRequest.Single(message: message) - ) { response in - switch response.accepted { - case .failure(let error): - try assertEqual(error.code.rawValue, self.expectedCode) - try assertEqual(error.message, self.expectedMessage) - case .success: - throw AssertionFailure( - message: - "The client should receive an error with the status code and message sent by the client." - ) - } - } - - let request = ClientRequest.Stream { writer in - let message = Grpc_Testing_StreamingOutputCallRequest.with { - $0.responseStatus = Grpc_Testing_EchoStatus.with { - $0.code = Int32(self.expectedCode) - $0.message = self.expectedMessage - } - } - try await writer.write(message) - } - - try await testServiceClient.fullDuplexCall(request: request) { response in - do { - for try await _ in response.messages { - throw AssertionFailure( - message: - "The client should receive an error with the status code and message sent by the client." - ) - } - } catch let error as RPCError { - try assertEqual(error.code.rawValue, self.expectedCode) - try assertEqual(error.message, self.expectedMessage) - } - } - } -} - -/// This test verifies Unicode and whitespace is correctly processed in status message. "\t" is -/// horizontal tab. "\r" is carriage return. "\n" is line feed. -/// -/// Server features: -/// - UnaryCall -/// - Echo Status -/// -/// Procedure: -/// 1. Client calls UnaryCall with: -/// ``` -/// { -/// response_status:{ -/// code: 2 -/// message: "\t\ntest with whitespace\r\nand Unicode BMP ☺ and non-BMP 😈\t\n" -/// } -/// } -/// ``` -/// -/// Client asserts: -/// - received status code is the same as the sent code for Procedure step 1 -/// - received status message is the same as the sent message for Procedure step 1, including all -/// whitespace characters -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -struct SpecialStatusMessage: InteroperabilityTest { - func run(client: GRPCClient) async throws { - let testServiceClient = Grpc_Testing_TestService.Client(wrapping: client) - - let responseMessage = "\t\ntest with whitespace\r\nand Unicode BMP ☺ and non-BMP 😈\t\n" - let message = Grpc_Testing_SimpleRequest.with { - $0.responseStatus = Grpc_Testing_EchoStatus.with { - $0.code = 2 - $0.message = responseMessage - } - } - try await testServiceClient.unaryCall( - request: ClientRequest.Single(message: message) - ) { response in - switch response.accepted { - case .success: - throw AssertionFailure( - message: "The response should be an error with the error code 2." - ) - case .failure(let error): - try assertEqual(error.code.rawValue, 2) - try assertEqual(error.message, responseMessage) - } - } - } -} - -/// This test verifies that calling an unimplemented RPC method returns the UNIMPLEMENTED status -/// code. -/// -/// Server features: N/A -/// -/// Procedure: -/// 1. Client calls grpc.testing.TestService/UnimplementedCall with an empty request (defined as -/// grpc.testing.Empty): -/// ``` -/// { -/// } -/// ``` -/// -/// Client asserts: -/// - received status code is 12 (UNIMPLEMENTED) -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -struct UnimplementedMethod: InteroperabilityTest { - func run(client: GRPCClient) async throws { - let testServiceClient = Grpc_Testing_TestService.Client(wrapping: client) - try await testServiceClient.unimplementedCall( - request: ClientRequest.Single(message: Grpc_Testing_Empty()) - ) { response in - switch response.accepted { - case .success: - throw AssertionFailure( - message: "The result should be an error." - ) - case .failure(let error): - try assertEqual(error.code, .unimplemented) - } - } - } -} - -/// This test verifies calling an unimplemented server returns the UNIMPLEMENTED status code. -/// -/// Server features: N/A -/// -/// Procedure: -/// 1. Client calls grpc.testing.UnimplementedService/UnimplementedCall with an empty request -/// (defined as grpc.testing.Empty): -/// ``` -/// { -/// } -/// ``` -/// -/// Client asserts: -/// - received status code is 12 (UNIMPLEMENTED) -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -struct UnimplementedService: InteroperabilityTest { - func run(client: GRPCClient) async throws { - let unimplementedServiceClient = Grpc_Testing_UnimplementedService.Client(wrapping: client) - try await unimplementedServiceClient.unimplementedCall( - request: ClientRequest.Single(message: Grpc_Testing_Empty()) - ) { response in - switch response.accepted { - case .success: - throw AssertionFailure(message: "The result should be an error.") - case .failure(let error): - try assertEqual(error.code, .unimplemented) - } - } - } -} diff --git a/Sources/InteroperabilityTests/TestService.swift b/Sources/InteroperabilityTests/TestService.swift deleted file mode 100644 index f4c79b784..000000000 --- a/Sources/InteroperabilityTests/TestService.swift +++ /dev/null @@ -1,244 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -private import Foundation -public import GRPCCore - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -public struct TestService: Grpc_Testing_TestService.ServiceProtocol { - public init() {} - - public func unimplementedCall( - request: ServerRequest.Single, - context: ServerContext - ) async throws -> ServerResponse.Single { - throw RPCError(code: .unimplemented, message: "The RPC is not implemented.") - } - - /// Server implements `emptyCall` which immediately returns the empty message. - public func emptyCall( - request: ServerRequest.Single, - context: ServerContext - ) async throws -> ServerResponse.Single { - let message = Grpc_Testing_Empty() - let (initialMetadata, trailingMetadata) = request.metadata.makeInitialAndTrailingMetadata() - return ServerResponse.Single( - message: message, - metadata: initialMetadata, - trailingMetadata: trailingMetadata - ) - } - - /// Server implements `unaryCall` which immediately returns a `SimpleResponse` with a payload - /// body of size `SimpleRequest.responseSize` bytes and type as appropriate for the - /// `SimpleRequest.responseType`. - /// - /// If the server does not support the `responseType`, then it should fail the RPC with - /// `INVALID_ARGUMENT`. - public func unaryCall( - request: ServerRequest.Single, - context: ServerContext - ) async throws -> ServerResponse.Single { - // We can't validate messages at the wire-encoding layer (i.e. where the compression byte is - // set), so we have to check via the encoding header. Note that it is possible for the header - // to be set and for the message to not be compressed. - let isRequestCompressed = - request.metadata["grpc-encoding"].filter({ $0 != "identity" }).count > 0 - if request.message.expectCompressed.value, !isRequestCompressed { - throw RPCError( - code: .invalidArgument, - message: "Expected compressed request, but 'grpc-encoding' was missing" - ) - } - - // If the request has a responseStatus set, the server should return that status. - // If the code is an error code, the server will throw an error containing that code - // and the message set in the responseStatus. - // If the code is `ok`, the server will automatically send back an `ok` status. - if request.message.responseStatus.isInitialized { - guard let code = Status.Code(rawValue: Int(request.message.responseStatus.code)) else { - throw RPCError(code: .invalidArgument, message: "The response status code is invalid.") - } - let status = Status( - code: code, - message: request.message.responseStatus.message - ) - if let error = RPCError(status: status) { - throw error - } - } - - if case .UNRECOGNIZED = request.message.responseType { - throw RPCError(code: .invalidArgument, message: "The response type is not recognized.") - } - - let responseMessage = Grpc_Testing_SimpleResponse.with { response in - response.payload = Grpc_Testing_Payload.with { payload in - payload.body = Data(repeating: 0, count: Int(request.message.responseSize)) - payload.type = request.message.responseType - } - } - - let (initialMetadata, trailingMetadata) = request.metadata.makeInitialAndTrailingMetadata() - - return ServerResponse.Single( - message: responseMessage, - metadata: initialMetadata, - trailingMetadata: trailingMetadata - ) - } - - /// Server gets the default `SimpleRequest` proto as the request. The content of the request is - /// ignored. It returns the `SimpleResponse` proto with the payload set to current timestamp. - /// The timestamp is an integer representing current time with nanosecond resolution. This - /// integer is formated as ASCII decimal in the response. The format is not really important as - /// long as the response payload is different for each request. In addition it adds cache control - /// headers such that the response can be cached by proxies in the response path. Server should - /// be behind a caching proxy for this test to pass. Currently we set the max-age to 60 seconds. - public func cacheableUnaryCall( - request: ServerRequest.Single, - context: ServerContext - ) async throws -> ServerResponse.Single { - throw RPCError(code: .unimplemented, message: "The RPC is not implemented.") - } - - /// Server implements `streamingOutputCall` by replying, in order, with one - /// `StreamingOutputCallResponse` for each `ResponseParameter`s in `StreamingOutputCallRequest`. - /// Each `StreamingOutputCallResponse` should have a payload body of size `ResponseParameter.size` - /// bytes, as specified by its respective `ResponseParameter`. After sending all responses, it - /// closes with OK. - public func streamingOutputCall( - request: ServerRequest.Single, - context: ServerContext - ) async throws -> ServerResponse.Stream { - let (initialMetadata, trailingMetadata) = request.metadata.makeInitialAndTrailingMetadata() - return ServerResponse.Stream(metadata: initialMetadata) { writer in - for responseParameter in request.message.responseParameters { - let response = Grpc_Testing_StreamingOutputCallResponse.with { response in - response.payload = Grpc_Testing_Payload.with { payload in - payload.body = Data(repeating: 0, count: Int(responseParameter.size)) - } - } - try await writer.write(response) - // We convert the `intervalUs` value from microseconds to nanoseconds. - try await Task.sleep(nanoseconds: UInt64(responseParameter.intervalUs) * 1000) - } - return trailingMetadata - } - } - - /// Server implements `streamingInputCall` which upon half close immediately returns a - /// `StreamingInputCallResponse` where `aggregatedPayloadSize` is the sum of all request payload - /// bodies received. - public func streamingInputCall( - request: ServerRequest.Stream, - context: ServerContext - ) async throws -> ServerResponse.Single { - let isRequestCompressed = - request.metadata["grpc-encoding"].filter({ $0 != "identity" }).count > 0 - var aggregatedPayloadSize = 0 - - for try await message in request.messages { - // We can't validate messages at the wire-encoding layer (i.e. where the compression byte is - // set), so we have to check via the encoding header. Note that it is possible for the header - // to be set and for the message to not be compressed. - if message.expectCompressed.value, !isRequestCompressed { - throw RPCError( - code: .invalidArgument, - message: "Expected compressed request, but 'grpc-encoding' was missing" - ) - } - - aggregatedPayloadSize += message.payload.body.count - } - - let responseMessage = Grpc_Testing_StreamingInputCallResponse.with { - $0.aggregatedPayloadSize = Int32(aggregatedPayloadSize) - } - - let (initialMetadata, trailingMetadata) = request.metadata.makeInitialAndTrailingMetadata() - return ServerResponse.Single( - message: responseMessage, - metadata: initialMetadata, - trailingMetadata: trailingMetadata - ) - } - - /// Server implements `fullDuplexCall` by replying, in order, with one - /// `StreamingOutputCallResponse` for each `ResponseParameter`s in each - /// `StreamingOutputCallRequest`. Each `StreamingOutputCallResponse` should have a payload body - /// of size `ResponseParameter.size` bytes, as specified by its respective `ResponseParameter`s. - /// After receiving half close and sending all responses, it closes with OK. - public func fullDuplexCall( - request: ServerRequest.Stream, - context: ServerContext - ) async throws -> ServerResponse.Stream { - let (initialMetadata, trailingMetadata) = request.metadata.makeInitialAndTrailingMetadata() - return ServerResponse.Stream(metadata: initialMetadata) { writer in - for try await message in request.messages { - // If a request message has a responseStatus set, the server should return that status. - // If the code is an error code, the server will throw an error containing that code - // and the message set in the responseStatus. - // If the code is `ok`, the server will automatically send back an `ok` status with the response. - if message.responseStatus.isInitialized { - guard let code = Status.Code(rawValue: Int(message.responseStatus.code)) else { - throw RPCError(code: .invalidArgument, message: "The response status code is invalid.") - } - - let status = Status(code: code, message: message.responseStatus.message) - if let error = RPCError(status: status) { - throw error - } - } - - for responseParameter in message.responseParameters { - let response = Grpc_Testing_StreamingOutputCallResponse.with { response in - response.payload = Grpc_Testing_Payload.with { - $0.body = Data(count: Int(responseParameter.size)) - } - } - try await writer.write(response) - } - } - return trailingMetadata - } - } - - /// This is not implemented as it is not described in the specification. - /// - /// See: https://github.com/grpc/grpc/blob/master/doc/interop-test-descriptions.md - public func halfDuplexCall( - request: ServerRequest.Stream, - context: ServerContext - ) async throws -> ServerResponse.Stream { - throw RPCError(code: .unimplemented, message: "The RPC is not implemented.") - } -} - -extension Metadata { - fileprivate func makeInitialAndTrailingMetadata() -> (Metadata, Metadata) { - var initialMetadata = Metadata() - var trailingMetadata = Metadata() - for value in self[stringValues: "x-grpc-test-echo-initial"] { - initialMetadata.addString(value, forKey: "x-grpc-test-echo-initial") - } - for value in self[binaryValues: "x-grpc-test-echo-trailing-bin"] { - trailingMetadata.addBinary(value, forKey: "x-grpc-test-echo-trailing-bin") - } - - return (initialMetadata, trailingMetadata) - } -} diff --git a/Sources/Services/Health/Generated/health.grpc.swift b/Sources/Services/Health/Generated/health.grpc.swift deleted file mode 100644 index a2f625c74..000000000 --- a/Sources/Services/Health/Generated/health.grpc.swift +++ /dev/null @@ -1,419 +0,0 @@ -// Copyright 2015 The gRPC Authors -// -// 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. - -// The canonical version of this proto can be found at -// https://github.com/grpc/grpc-proto/blob/master/grpc/health/v1/health.proto - -// DO NOT EDIT. -// swift-format-ignore-file -// -// Generated by the gRPC Swift generator plugin for the protocol buffer compiler. -// Source: health.proto -// -// For information on using the generated types, please see the documentation: -// https://github.com/grpc/grpc-swift - -package import GRPCCore -internal import GRPCProtobuf - -package enum Grpc_Health_V1_Health { - package static let descriptor = GRPCCore.ServiceDescriptor.grpc_health_v1_Health - package enum Method { - package enum Check { - package typealias Input = Grpc_Health_V1_HealthCheckRequest - package typealias Output = Grpc_Health_V1_HealthCheckResponse - package static let descriptor = GRPCCore.MethodDescriptor( - service: Grpc_Health_V1_Health.descriptor.fullyQualifiedService, - method: "Check" - ) - } - package enum Watch { - package typealias Input = Grpc_Health_V1_HealthCheckRequest - package typealias Output = Grpc_Health_V1_HealthCheckResponse - package static let descriptor = GRPCCore.MethodDescriptor( - service: Grpc_Health_V1_Health.descriptor.fullyQualifiedService, - method: "Watch" - ) - } - package static let descriptors: [GRPCCore.MethodDescriptor] = [ - Check.descriptor, - Watch.descriptor - ] - } - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - package typealias StreamingServiceProtocol = Grpc_Health_V1_HealthStreamingServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - package typealias ServiceProtocol = Grpc_Health_V1_HealthServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - package typealias ClientProtocol = Grpc_Health_V1_HealthClientProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - package typealias Client = Grpc_Health_V1_HealthClient -} - -extension GRPCCore.ServiceDescriptor { - package static let grpc_health_v1_Health = Self( - package: "grpc.health.v1", - service: "Health" - ) -} - -/// Health is gRPC's mechanism for checking whether a server is able to handle -/// RPCs. Its semantics are documented in -/// https://github.com/grpc/grpc/blob/master/doc/health-checking.md. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -package protocol Grpc_Health_V1_HealthStreamingServiceProtocol: GRPCCore.RegistrableRPCService { - /// Check gets the health of the specified service. If the requested service - /// is unknown, the call will fail with status NOT_FOUND. If the caller does - /// not specify a service name, the server should respond with its overall - /// health status. - /// - /// Clients should set a deadline when calling Check, and can declare the - /// server unhealthy if they do not receive a timely response. - /// - /// Check implementations should be idempotent and side effect free. - func check( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream - - /// Performs a watch for the serving status of the requested service. - /// The server will immediately send back a message indicating the current - /// serving status. It will then subsequently send a new message whenever - /// the service's serving status changes. - /// - /// If the requested service is unknown when the call is received, the - /// server will send a message setting the serving status to - /// SERVICE_UNKNOWN but will *not* terminate the call. If at some - /// future point, the serving status of the service becomes known, the - /// server will send a new message with the service's serving status. - /// - /// If the call terminates with status UNIMPLEMENTED, then clients - /// should assume this method is not supported and should not retry the - /// call. If the call terminates with any other status (including OK), - /// clients should retry the call with appropriate exponential backoff. - func watch( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream -} - -/// Conformance to `GRPCCore.RegistrableRPCService`. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension Grpc_Health_V1_Health.StreamingServiceProtocol { - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - package func registerMethods(with router: inout GRPCCore.RPCRouter) { - router.registerHandler( - forMethod: Grpc_Health_V1_Health.Method.Check.descriptor, - deserializer: GRPCProtobuf.ProtobufDeserializer(), - serializer: GRPCProtobuf.ProtobufSerializer(), - handler: { request, context in - try await self.check( - request: request, - context: context - ) - } - ) - router.registerHandler( - forMethod: Grpc_Health_V1_Health.Method.Watch.descriptor, - deserializer: GRPCProtobuf.ProtobufDeserializer(), - serializer: GRPCProtobuf.ProtobufSerializer(), - handler: { request, context in - try await self.watch( - request: request, - context: context - ) - } - ) - } -} - -/// Health is gRPC's mechanism for checking whether a server is able to handle -/// RPCs. Its semantics are documented in -/// https://github.com/grpc/grpc/blob/master/doc/health-checking.md. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -package protocol Grpc_Health_V1_HealthServiceProtocol: Grpc_Health_V1_Health.StreamingServiceProtocol { - /// Check gets the health of the specified service. If the requested service - /// is unknown, the call will fail with status NOT_FOUND. If the caller does - /// not specify a service name, the server should respond with its overall - /// health status. - /// - /// Clients should set a deadline when calling Check, and can declare the - /// server unhealthy if they do not receive a timely response. - /// - /// Check implementations should be idempotent and side effect free. - func check( - request: GRPCCore.ServerRequest.Single, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Single - - /// Performs a watch for the serving status of the requested service. - /// The server will immediately send back a message indicating the current - /// serving status. It will then subsequently send a new message whenever - /// the service's serving status changes. - /// - /// If the requested service is unknown when the call is received, the - /// server will send a message setting the serving status to - /// SERVICE_UNKNOWN but will *not* terminate the call. If at some - /// future point, the serving status of the service becomes known, the - /// server will send a new message with the service's serving status. - /// - /// If the call terminates with status UNIMPLEMENTED, then clients - /// should assume this method is not supported and should not retry the - /// call. If the call terminates with any other status (including OK), - /// clients should retry the call with appropriate exponential backoff. - func watch( - request: GRPCCore.ServerRequest.Single, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream -} - -/// Partial conformance to `Grpc_Health_V1_HealthStreamingServiceProtocol`. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension Grpc_Health_V1_Health.ServiceProtocol { - package func check( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream { - let response = try await self.check( - request: GRPCCore.ServerRequest.Single(stream: request), - context: context - ) - return GRPCCore.ServerResponse.Stream(single: response) - } - - package func watch( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream { - let response = try await self.watch( - request: GRPCCore.ServerRequest.Single(stream: request), - context: context - ) - return response - } -} - -/// Health is gRPC's mechanism for checking whether a server is able to handle -/// RPCs. Its semantics are documented in -/// https://github.com/grpc/grpc/blob/master/doc/health-checking.md. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -package protocol Grpc_Health_V1_HealthClientProtocol: Sendable { - /// Check gets the health of the specified service. If the requested service - /// is unknown, the call will fail with status NOT_FOUND. If the caller does - /// not specify a service name, the server should respond with its overall - /// health status. - /// - /// Clients should set a deadline when calling Check, and can declare the - /// server unhealthy if they do not receive a timely response. - /// - /// Check implementations should be idempotent and side effect free. - func check( - request: GRPCCore.ClientRequest.Single, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R - ) async throws -> R where R: Sendable - - /// Performs a watch for the serving status of the requested service. - /// The server will immediately send back a message indicating the current - /// serving status. It will then subsequently send a new message whenever - /// the service's serving status changes. - /// - /// If the requested service is unknown when the call is received, the - /// server will send a message setting the serving status to - /// SERVICE_UNKNOWN but will *not* terminate the call. If at some - /// future point, the serving status of the service becomes known, the - /// server will send a new message with the service's serving status. - /// - /// If the call terminates with status UNIMPLEMENTED, then clients - /// should assume this method is not supported and should not retry the - /// call. If the call terminates with any other status (including OK), - /// clients should retry the call with appropriate exponential backoff. - func watch( - request: GRPCCore.ClientRequest.Single, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Stream) async throws -> R - ) async throws -> R where R: Sendable -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension Grpc_Health_V1_Health.ClientProtocol { - package func check( - request: GRPCCore.ClientRequest.Single, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R = { - try $0.message - } - ) async throws -> R where R: Sendable { - try await self.check( - request: request, - serializer: GRPCProtobuf.ProtobufSerializer(), - deserializer: GRPCProtobuf.ProtobufDeserializer(), - options: options, - body - ) - } - - package func watch( - request: GRPCCore.ClientRequest.Single, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Stream) async throws -> R - ) async throws -> R where R: Sendable { - try await self.watch( - request: request, - serializer: GRPCProtobuf.ProtobufSerializer(), - deserializer: GRPCProtobuf.ProtobufDeserializer(), - options: options, - body - ) - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension Grpc_Health_V1_Health.ClientProtocol { - /// Check gets the health of the specified service. If the requested service - /// is unknown, the call will fail with status NOT_FOUND. If the caller does - /// not specify a service name, the server should respond with its overall - /// health status. - /// - /// Clients should set a deadline when calling Check, and can declare the - /// server unhealthy if they do not receive a timely response. - /// - /// Check implementations should be idempotent and side effect free. - package func check( - _ message: Grpc_Health_V1_HealthCheckRequest, - metadata: GRPCCore.Metadata = [:], - options: GRPCCore.CallOptions = .defaults, - onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> Result = { - try $0.message - } - ) async throws -> Result where Result: Sendable { - let request = GRPCCore.ClientRequest.Single( - message: message, - metadata: metadata - ) - return try await self.check( - request: request, - options: options, - handleResponse - ) - } - - /// Performs a watch for the serving status of the requested service. - /// The server will immediately send back a message indicating the current - /// serving status. It will then subsequently send a new message whenever - /// the service's serving status changes. - /// - /// If the requested service is unknown when the call is received, the - /// server will send a message setting the serving status to - /// SERVICE_UNKNOWN but will *not* terminate the call. If at some - /// future point, the serving status of the service becomes known, the - /// server will send a new message with the service's serving status. - /// - /// If the call terminates with status UNIMPLEMENTED, then clients - /// should assume this method is not supported and should not retry the - /// call. If the call terminates with any other status (including OK), - /// clients should retry the call with appropriate exponential backoff. - package func watch( - _ message: Grpc_Health_V1_HealthCheckRequest, - metadata: GRPCCore.Metadata = [:], - options: GRPCCore.CallOptions = .defaults, - onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse.Stream) async throws -> Result - ) async throws -> Result where Result: Sendable { - let request = GRPCCore.ClientRequest.Single( - message: message, - metadata: metadata - ) - return try await self.watch( - request: request, - options: options, - handleResponse - ) - } -} - -/// Health is gRPC's mechanism for checking whether a server is able to handle -/// RPCs. Its semantics are documented in -/// https://github.com/grpc/grpc/blob/master/doc/health-checking.md. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -package struct Grpc_Health_V1_HealthClient: Grpc_Health_V1_Health.ClientProtocol { - private let client: GRPCCore.GRPCClient - - package init(wrapping client: GRPCCore.GRPCClient) { - self.client = client - } - - /// Check gets the health of the specified service. If the requested service - /// is unknown, the call will fail with status NOT_FOUND. If the caller does - /// not specify a service name, the server should respond with its overall - /// health status. - /// - /// Clients should set a deadline when calling Check, and can declare the - /// server unhealthy if they do not receive a timely response. - /// - /// Check implementations should be idempotent and side effect free. - package func check( - request: GRPCCore.ClientRequest.Single, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R = { - try $0.message - } - ) async throws -> R where R: Sendable { - try await self.client.unary( - request: request, - descriptor: Grpc_Health_V1_Health.Method.Check.descriptor, - serializer: serializer, - deserializer: deserializer, - options: options, - handler: body - ) - } - - /// Performs a watch for the serving status of the requested service. - /// The server will immediately send back a message indicating the current - /// serving status. It will then subsequently send a new message whenever - /// the service's serving status changes. - /// - /// If the requested service is unknown when the call is received, the - /// server will send a message setting the serving status to - /// SERVICE_UNKNOWN but will *not* terminate the call. If at some - /// future point, the serving status of the service becomes known, the - /// server will send a new message with the service's serving status. - /// - /// If the call terminates with status UNIMPLEMENTED, then clients - /// should assume this method is not supported and should not retry the - /// call. If the call terminates with any other status (including OK), - /// clients should retry the call with appropriate exponential backoff. - package func watch( - request: GRPCCore.ClientRequest.Single, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Stream) async throws -> R - ) async throws -> R where R: Sendable { - try await self.client.serverStreaming( - request: request, - descriptor: Grpc_Health_V1_Health.Method.Watch.descriptor, - serializer: serializer, - deserializer: deserializer, - options: options, - handler: body - ) - } -} \ No newline at end of file diff --git a/Sources/Services/Health/Generated/health.pb.swift b/Sources/Services/Health/Generated/health.pb.swift deleted file mode 100644 index ea2cde5c6..000000000 --- a/Sources/Services/Health/Generated/health.pb.swift +++ /dev/null @@ -1,183 +0,0 @@ -// DO NOT EDIT. -// swift-format-ignore-file -// swiftlint:disable all -// -// Generated by the Swift generator plugin for the protocol buffer compiler. -// Source: health.proto -// -// For information on using the generated types, please see the documentation: -// https://github.com/apple/swift-protobuf/ - -// Copyright 2015 The gRPC Authors -// -// 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. - -// The canonical version of this proto can be found at -// https://github.com/grpc/grpc-proto/blob/master/grpc/health/v1/health.proto - -package import SwiftProtobuf - -// If the compiler emits an error on this type, it is because this file -// was generated by a version of the `protoc` Swift plug-in that is -// incompatible with the version of SwiftProtobuf to which you are linking. -// Please ensure that you are building against the same version of the API -// that was used to generate this file. -fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { - struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} - typealias Version = _2 -} - -package struct Grpc_Health_V1_HealthCheckRequest: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - package var service: String = String() - - package var unknownFields = SwiftProtobuf.UnknownStorage() - - package init() {} -} - -package struct Grpc_Health_V1_HealthCheckResponse: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - package var status: Grpc_Health_V1_HealthCheckResponse.ServingStatus = .unknown - - package var unknownFields = SwiftProtobuf.UnknownStorage() - - package enum ServingStatus: SwiftProtobuf.Enum, Swift.CaseIterable { - package typealias RawValue = Int - case unknown // = 0 - case serving // = 1 - case notServing // = 2 - - /// Used only by the Watch method. - case serviceUnknown // = 3 - case UNRECOGNIZED(Int) - - package init() { - self = .unknown - } - - package init?(rawValue: Int) { - switch rawValue { - case 0: self = .unknown - case 1: self = .serving - case 2: self = .notServing - case 3: self = .serviceUnknown - default: self = .UNRECOGNIZED(rawValue) - } - } - - package var rawValue: Int { - switch self { - case .unknown: return 0 - case .serving: return 1 - case .notServing: return 2 - case .serviceUnknown: return 3 - case .UNRECOGNIZED(let i): return i - } - } - - // The compiler won't synthesize support with the UNRECOGNIZED case. - package static let allCases: [Grpc_Health_V1_HealthCheckResponse.ServingStatus] = [ - .unknown, - .serving, - .notServing, - .serviceUnknown, - ] - - } - - package init() {} -} - -// MARK: - Code below here is support for the SwiftProtobuf runtime. - -fileprivate let _protobuf_package = "grpc.health.v1" - -extension Grpc_Health_V1_HealthCheckRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - package static let protoMessageName: String = _protobuf_package + ".HealthCheckRequest" - package static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "service"), - ] - - package mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularStringField(value: &self.service) }() - default: break - } - } - } - - package func traverse(visitor: inout V) throws { - if !self.service.isEmpty { - try visitor.visitSingularStringField(value: self.service, fieldNumber: 1) - } - try unknownFields.traverse(visitor: &visitor) - } - - package static func ==(lhs: Grpc_Health_V1_HealthCheckRequest, rhs: Grpc_Health_V1_HealthCheckRequest) -> Bool { - if lhs.service != rhs.service {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Health_V1_HealthCheckResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - package static let protoMessageName: String = _protobuf_package + ".HealthCheckResponse" - package static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "status"), - ] - - package mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularEnumField(value: &self.status) }() - default: break - } - } - } - - package func traverse(visitor: inout V) throws { - if self.status != .unknown { - try visitor.visitSingularEnumField(value: self.status, fieldNumber: 1) - } - try unknownFields.traverse(visitor: &visitor) - } - - package static func ==(lhs: Grpc_Health_V1_HealthCheckResponse, rhs: Grpc_Health_V1_HealthCheckResponse) -> Bool { - if lhs.status != rhs.status {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Health_V1_HealthCheckResponse.ServingStatus: SwiftProtobuf._ProtoNameProviding { - package static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 0: .same(proto: "UNKNOWN"), - 1: .same(proto: "SERVING"), - 2: .same(proto: "NOT_SERVING"), - 3: .same(proto: "SERVICE_UNKNOWN"), - ] -} diff --git a/Sources/Services/Health/Health.swift b/Sources/Services/Health/Health.swift deleted file mode 100644 index 641de83dd..000000000 --- a/Sources/Services/Health/Health.swift +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -public import GRPCCore - -/// ``Health`` is gRPC’s mechanism for checking whether a server is able to handle RPCs. Its semantics are documented in -/// https://github.com/grpc/grpc/blob/master/doc/health-checking.md. -/// -/// `Health` initializes a new ``Health/Service-swift.struct`` and ``Health/Provider-swift.struct``. -/// - `Health.Service` implements the Health service from the `grpc.health.v1` package and can be registered with a server -/// like any other service. -/// - `Health.Provider` provides status updates to `Health.Service`. `Health.Service` doesn't know about the other -/// services running on a server so it must be provided with status updates via `Health.Provider`. To make specifying the service -/// being updated easier, the generated code for services includes an extension to `ServiceDescriptor`. -/// -/// The following shows an example of initializing a Health service and updating the status of the `Foo` service in the `bar` package. -/// -/// ```swift -/// let health = Health() -/// let server = GRPCServer( -/// transport: transport, -/// services: [health.service, FooService()] -/// ) -/// -/// health.provider.updateStatus( -/// .serving, -/// forService: .bar_Foo -/// ) -/// ``` -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -public struct Health: Sendable { - /// An implementation of the `grpc.health.v1.Health` service. - public let service: Health.Service - - /// Provides status updates to the Health service. - public let provider: Health.Provider - - /// Constructs a new ``Health``, initializing a ``Health/Service-swift.struct`` and a - /// ``Health/Provider-swift.struct``. - public init() { - let healthService = HealthService() - - self.service = Health.Service(healthService: healthService) - self.provider = Health.Provider(healthService: healthService) - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension Health { - /// An implementation of the `grpc.health.v1.Health` service. - public struct Service: RegistrableRPCService, Sendable { - private let healthService: HealthService - - public func registerMethods(with router: inout RPCRouter) { - self.healthService.registerMethods(with: &router) - } - - fileprivate init(healthService: HealthService) { - self.healthService = healthService - } - } - - /// Provides status updates to ``Health/Service-swift.struct``. - public struct Provider: Sendable { - private let healthService: HealthService - - /// Updates the status of a service. - /// - /// - Parameters: - /// - status: The status of the service. - /// - service: The description of the service. - public func updateStatus( - _ status: ServingStatus, - forService service: ServiceDescriptor - ) { - self.healthService.updateStatus( - Grpc_Health_V1_HealthCheckResponse.ServingStatus(status), - forService: service.fullyQualifiedService - ) - } - - /// Updates the status of a service. - /// - /// - Parameters: - /// - status: The status of the service. - /// - service: The fully qualified service name in the format: - /// - "package.service": if the service is part of a package. For example, "helloworld.Greeter". - /// - "service": if the service is not part of a package. For example, "Greeter". - public func updateStatus( - _ status: ServingStatus, - forService service: String - ) { - self.healthService.updateStatus( - Grpc_Health_V1_HealthCheckResponse.ServingStatus(status), - forService: service - ) - } - - fileprivate init(healthService: HealthService) { - self.healthService = healthService - } - } -} - -extension Grpc_Health_V1_HealthCheckResponse.ServingStatus { - package init(_ status: ServingStatus) { - switch status.value { - case .serving: - self = .serving - case .notServing: - self = .notServing - } - } -} diff --git a/Sources/Services/Health/HealthService.swift b/Sources/Services/Health/HealthService.swift deleted file mode 100644 index 362e707f2..000000000 --- a/Sources/Services/Health/HealthService.swift +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -internal import GRPCCore -private import Synchronization - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -internal struct HealthService: Grpc_Health_V1_HealthServiceProtocol { - private let state = HealthService.State() - - func check( - request: ServerRequest.Single, - context: ServerContext - ) async throws -> ServerResponse.Single { - let service = request.message.service - - guard let status = self.state.currentStatus(ofService: service) else { - throw RPCError(code: .notFound, message: "Requested service unknown.") - } - - var response = Grpc_Health_V1_HealthCheckResponse() - response.status = status - - return ServerResponse.Single(message: response) - } - - func watch( - request: ServerRequest.Single, - context: ServerContext - ) async -> ServerResponse.Stream { - let service = request.message.service - let statuses = AsyncStream.makeStream(of: Grpc_Health_V1_HealthCheckResponse.ServingStatus.self) - - self.state.addContinuation(statuses.continuation, forService: service) - - return ServerResponse.Stream(of: Grpc_Health_V1_HealthCheckResponse.self) { writer in - var response = Grpc_Health_V1_HealthCheckResponse() - - for await status in statuses.stream { - response.status = status - try await writer.write(response) - } - - return [:] - } - } - - func updateStatus( - _ status: Grpc_Health_V1_HealthCheckResponse.ServingStatus, - forService service: String - ) { - self.state.updateStatus(status, forService: service) - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension HealthService { - private final class State: Sendable { - // The state of each service keyed by the fully qualified service name. - private let lockedStorage = Mutex([String: ServiceState]()) - - fileprivate func currentStatus( - ofService service: String - ) -> Grpc_Health_V1_HealthCheckResponse.ServingStatus? { - return self.lockedStorage.withLock { $0[service]?.currentStatus } - } - - fileprivate func updateStatus( - _ status: Grpc_Health_V1_HealthCheckResponse.ServingStatus, - forService service: String - ) { - self.lockedStorage.withLock { storage in - storage[service, default: ServiceState(status: status)].updateStatus(status) - } - } - - fileprivate func addContinuation( - _ continuation: AsyncStream.Continuation, - forService service: String - ) { - self.lockedStorage.withLock { storage in - storage[service, default: ServiceState(status: .serviceUnknown)] - .addContinuation(continuation) - } - } - } - - // Encapsulates the current status of a service and the continuations of its watch streams. - private struct ServiceState: Sendable { - private(set) var currentStatus: Grpc_Health_V1_HealthCheckResponse.ServingStatus - private var continuations: - [AsyncStream.Continuation] - - fileprivate mutating func updateStatus( - _ status: Grpc_Health_V1_HealthCheckResponse.ServingStatus - ) { - guard status != self.currentStatus else { - return - } - - self.currentStatus = status - - for continuation in self.continuations { - continuation.yield(status) - } - } - - fileprivate mutating func addContinuation( - _ continuation: AsyncStream.Continuation - ) { - self.continuations.append(continuation) - continuation.yield(self.currentStatus) - } - - fileprivate init(status: Grpc_Health_V1_HealthCheckResponse.ServingStatus = .unknown) { - self.currentStatus = status - self.continuations = [] - } - } -} diff --git a/Sources/Services/Health/ServingStatus.swift b/Sources/Services/Health/ServingStatus.swift deleted file mode 100644 index cc0fd5b15..000000000 --- a/Sources/Services/Health/ServingStatus.swift +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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. - */ - -/// The status of a service. -/// -/// - ``ServingStatus/serving`` indicates that a service is healthy. -/// - ``ServingStatus/notServing`` indicates that a service is unhealthy. -public struct ServingStatus: Sendable, Hashable { - internal enum Value: Sendable, Hashable { - case serving - case notServing - } - - /// A status indicating that a service is healthy. - public static let serving = ServingStatus(.serving) - - /// A status indicating that a service unhealthy. - public static let notServing = ServingStatus(.notServing) - - internal var value: Value - - private init(_ value: Value) { - self.value = value - } -} diff --git a/Sources/interoperability-tests/InteroperabilityTestsExecutable.swift b/Sources/interoperability-tests/InteroperabilityTestsExecutable.swift deleted file mode 100644 index 15e1ba0fa..000000000 --- a/Sources/interoperability-tests/InteroperabilityTestsExecutable.swift +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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 ArgumentParser -import GRPCCore -import GRPCHTTP2Core -import GRPCHTTP2TransportNIOPosix -import InteroperabilityTests -import NIOPosix - -@main -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -struct InteroperabilityTestsExecutable: AsyncParsableCommand { - static let configuration = CommandConfiguration( - abstract: "gRPC Swift Interoperability Runner", - subcommands: [StartServer.self, ListTests.self, RunTests.self] - ) - - struct StartServer: AsyncParsableCommand { - static let configuration = CommandConfiguration( - abstract: "Start the gRPC Swift interoperability test server." - ) - - @Option(help: "The port to listen on for new connections") - var port: Int - - func run() async throws { - let server = GRPCServer( - transport: .http2NIOPosix( - address: .ipv4(host: "0.0.0.0", port: self.port), - config: .defaults(transportSecurity: .plaintext) { - $0.compression.enabledAlgorithms = .all - } - ), - services: [TestService()] - ) - try await server.serve() - } - } - - struct ListTests: ParsableCommand { - static let configuration = CommandConfiguration( - abstract: "List all interoperability test names." - ) - - func run() throws { - for testCase in InteroperabilityTestCase.allCases { - print(testCase.name) - } - } - } - - struct RunTests: AsyncParsableCommand { - static let configuration = CommandConfiguration( - abstract: """ - Run gRPC interoperability tests using a gRPC Swift client. - You can specify a test name as an argument to run a single test. - If no test name is given, all interoperability tests will be run. - """ - ) - - @Option(help: "The host the server is running on") - var host: String - - @Option(help: "The port to connect to") - var port: Int - - @Argument(help: "The name of the tests to run. If none, all tests will be run.") - var testNames: [String] = InteroperabilityTestCase.allCases.map { $0.name } - - func run() async throws { - let client = try self.buildClient(host: self.host, port: self.port) - - try await withThrowingDiscardingTaskGroup { group in - group.addTask { - try await client.run() - } - - for testName in testNames { - guard let testCase = InteroperabilityTestCase(rawValue: testName) else { - print(InteroperabilityTestError.testNotFound(name: testName)) - continue - } - await self.runTest(testCase, using: client) - } - - client.beginGracefulShutdown() - } - } - - private func buildClient(host: String, port: Int) throws -> GRPCClient { - let serviceConfig = ServiceConfig(loadBalancingConfig: [.roundRobin]) - return GRPCClient( - transport: try .http2NIOPosix( - target: .ipv4(host: host, port: port), - config: .defaults(transportSecurity: .plaintext) { - $0.compression.enabledAlgorithms = .all - }, - serviceConfig: serviceConfig - ) - ) - } - - private func runTest( - _ testCase: InteroperabilityTestCase, - using client: GRPCClient - ) async { - print("Running '\(testCase.name)' ... ", terminator: "") - do { - try await testCase.makeTest().run(client: client) - print("PASSED") - } catch { - print("FAILED\n" + String(describing: InteroperabilityTestError.testFailed(cause: error))) - } - } - } -} - -enum InteroperabilityTestError: Error, CustomStringConvertible { - case testNotFound(name: String) - case testFailed(cause: any Error) - - var description: String { - switch self { - case .testNotFound(let name): - return "Test \"\(name)\" not found." - case .testFailed(let cause): - return "Test failed with error: \(String(describing: cause))" - } - } -} diff --git a/Sources/performance-worker/BenchmarkClient.swift b/Sources/performance-worker/BenchmarkClient.swift deleted file mode 100644 index 57afa894f..000000000 --- a/Sources/performance-worker/BenchmarkClient.swift +++ /dev/null @@ -1,242 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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 -import GRPCCore -import NIOConcurrencyHelpers -import Synchronization - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -final class BenchmarkClient: Sendable { - private let _isShuttingDown = Atomic(false) - - /// Whether the benchmark client is shutting down. Used to control when to stop sending messages - /// or creating new RPCs. - private var isShuttingDown: Bool { - self._isShuttingDown.load(ordering: .relaxed) - } - - /// The underlying client. - private let client: GRPCClient - - /// The number of concurrent RPCs to run. - private let concurrentRPCs: Int - - /// The type of RPC to make against the server. - private let rpcType: RPCType - - /// The max number of messages to send on a stream before replacing the RPC with a new one. A - /// value of zero means there is no limit. - private let messagesPerStream: Int - private var noMessageLimit: Bool { self.messagesPerStream == 0 } - - /// The message to send for all RPC types to the server. - private let message: Grpc_Testing_SimpleRequest - - /// Per RPC stats. - private let rpcStats: NIOLockedValueBox - - init( - client: GRPCClient, - concurrentRPCs: Int, - rpcType: RPCType, - messagesPerStream: Int, - protoParams: Grpc_Testing_SimpleProtoParams, - histogramParams: Grpc_Testing_HistogramParams? - ) { - self.client = client - self.concurrentRPCs = concurrentRPCs - self.messagesPerStream = messagesPerStream - self.rpcType = rpcType - self.message = .with { - $0.responseSize = protoParams.respSize - $0.payload = Grpc_Testing_Payload.with { - $0.body = Data(count: Int(protoParams.reqSize)) - } - } - - let histogram: RPCStats.LatencyHistogram - if let histogramParams = histogramParams { - histogram = RPCStats.LatencyHistogram( - resolution: histogramParams.resolution, - maxBucketStart: histogramParams.maxPossible - ) - } else { - histogram = RPCStats.LatencyHistogram() - } - - self.rpcStats = NIOLockedValueBox(RPCStats(latencyHistogram: histogram)) - } - - enum RPCType { - case unary - case streaming - } - - internal var currentStats: RPCStats { - return self.rpcStats.withLockedValue { stats in - return stats - } - } - - internal func run() async throws { - let benchmarkClient = Grpc_Testing_BenchmarkServiceClient(wrapping: self.client) - return try await withThrowingTaskGroup(of: Void.self) { clientGroup in - // Start the client. - clientGroup.addTask { - try await self.client.run() - } - - try await withThrowingTaskGroup(of: Void.self) { rpcsGroup in - // Start one task for each concurrent RPC and keep looping in that task until indicated - // to stop. - for _ in 0 ..< self.concurrentRPCs { - rpcsGroup.addTask { - while !self.isShuttingDown { - switch self.rpcType { - case .unary: - await self.unary(benchmark: benchmarkClient) - - case .streaming: - await self.streaming(benchmark: benchmarkClient) - } - } - } - } - - try await rpcsGroup.waitForAll() - } - - self.client.beginGracefulShutdown() - try await clientGroup.next() - } - } - - private func record(latencyNanos: Double, errorCode: RPCError.Code?) { - self.rpcStats.withLockedValue { stats in - stats.latencyHistogram.record(latencyNanos) - if let errorCode = errorCode { - stats.requestResultCount[errorCode, default: 0] += 1 - } - } - } - - private func record(errorCode: RPCError.Code) { - self.rpcStats.withLockedValue { stats in - stats.requestResultCount[errorCode, default: 0] += 1 - } - } - - private func timeIt( - _ body: () async throws -> R - ) async rethrows -> (R, nanoseconds: Double) { - let startTime = DispatchTime.now().uptimeNanoseconds - let result = try await body() - let endTime = DispatchTime.now().uptimeNanoseconds - return (result, nanoseconds: Double(endTime - startTime)) - } - - private func unary(benchmark: Grpc_Testing_BenchmarkServiceClient) async { - let (errorCode, nanoseconds): (RPCError.Code?, Double) = await self.timeIt { - do { - try await benchmark.unaryCall(request: ClientRequest.Single(message: self.message)) { - _ = try $0.message - } - return nil - } catch let error as RPCError { - return error.code - } catch { - return .unknown - } - } - - self.record(latencyNanos: nanoseconds, errorCode: errorCode) - } - - private func streaming(benchmark: Grpc_Testing_BenchmarkServiceClient) async { - // Streaming RPCs ping-pong messages back and forth. To achieve this the response message - // stream is sent to the request closure, and the request closure indicates the outcome back - // to the response handler to keep the RPC alive for the appropriate amount of time. - let status = AsyncStream.makeStream(of: RPCError.self) - let response = AsyncStream.makeStream( - of: RPCAsyncSequence.self - ) - - let request = ClientRequest.Stream(of: Grpc_Testing_SimpleRequest.self) { writer in - defer { status.continuation.finish() } - - // The time at which the last message was sent. - var lastMessageSendTime = DispatchTime.now() - try await writer.write(self.message) - - // Wait for the response stream. - var iterator = response.stream.makeAsyncIterator() - guard let responses = await iterator.next() else { - throw RPCError(code: .internalError, message: "") - } - - // Record the first latency. - let now = DispatchTime.now() - let nanos = now.uptimeNanoseconds - lastMessageSendTime.uptimeNanoseconds - lastMessageSendTime = now - self.record(latencyNanos: Double(nanos), errorCode: nil) - - // Now start looping. Only stop when the max messages per stream is hit or told to stop. - var responseIterator = responses.makeAsyncIterator() - var messagesSent = 1 - - while !self.isShuttingDown && (self.noMessageLimit || messagesSent < self.messagesPerStream) { - messagesSent += 1 - do { - if try await responseIterator.next() != nil { - let now = DispatchTime.now() - let nanos = now.uptimeNanoseconds - lastMessageSendTime.uptimeNanoseconds - lastMessageSendTime = now - self.record(latencyNanos: Double(nanos), errorCode: nil) - try await writer.write(self.message) - } else { - break - } - } catch let error as RPCError { - status.continuation.yield(error) - break - } catch { - status.continuation.yield(RPCError(code: .unknown, message: "")) - break - } - } - } - - do { - try await benchmark.streamingCall(request: request) { - response.continuation.yield($0.messages) - response.continuation.finish() - for await errorCode in status.stream { - throw errorCode - } - } - } catch let error as RPCError { - self.record(errorCode: error.code) - } catch { - self.record(errorCode: .unknown) - } - } - - internal func shutdown() { - self._isShuttingDown.store(true, ordering: .relaxed) - self.client.beginGracefulShutdown() - } -} diff --git a/Sources/performance-worker/BenchmarkService.swift b/Sources/performance-worker/BenchmarkService.swift deleted file mode 100644 index b73d46534..000000000 --- a/Sources/performance-worker/BenchmarkService.swift +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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 GRPCCore -import Synchronization - -import struct Foundation.Data - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -final class BenchmarkService: Grpc_Testing_BenchmarkService.ServiceProtocol { - /// Used to check if the server can be streaming responses. - private let working = Atomic(true) - - /// One request followed by one response. - /// The server returns a client payload with the size requested by the client. - func unaryCall( - request: ServerRequest.Single, - context: ServerContext - ) async throws -> ServerResponse.Single { - // Throw an error if the status is not `ok`. Otherwise, an `ok` status is automatically sent - // if the request is successful. - if request.message.responseStatus.isInitialized { - try self.checkOkStatus(request.message.responseStatus) - } - - return ServerResponse.Single( - message: .with { - $0.payload = Grpc_Testing_Payload.with { - $0.body = Data(count: Int(request.message.responseSize)) - } - } - ) - } - - /// Repeated sequence of one request followed by one response. - /// The server returns a payload with the size requested by the client for each received message. - func streamingCall( - request: ServerRequest.Stream, - context: ServerContext - ) async throws -> ServerResponse.Stream { - return ServerResponse.Stream { writer in - for try await message in request.messages { - if message.responseStatus.isInitialized { - try self.checkOkStatus(message.responseStatus) - } - - let responseMessage = Grpc_Testing_SimpleResponse.with { - $0.payload = Grpc_Testing_Payload.with { - $0.body = Data(count: Int(message.responseSize)) - } - } - - try await writer.write(responseMessage) - } - - return [:] - } - } - - /// Single-sided unbounded streaming from client to server. - /// The server returns a payload with the size requested by the client once the client does WritesDone. - func streamingFromClient( - request: ServerRequest.Stream, - context: ServerContext - ) async throws -> ServerResponse.Single { - var responseSize = 0 - for try await message in request.messages { - if message.responseStatus.isInitialized { - try self.checkOkStatus(message.responseStatus) - } - responseSize = Int(message.responseSize) - } - - return ServerResponse.Single( - message: .with { - $0.payload = .with { - $0.body = Data(count: responseSize) - } - } - ) - } - - /// Single-sided unbounded streaming from server to client. - /// The server repeatedly returns a payload with the size requested by the client. - func streamingFromServer( - request: ServerRequest.Single, - context: ServerContext - ) async throws -> ServerResponse.Stream { - if request.message.responseStatus.isInitialized { - try self.checkOkStatus(request.message.responseStatus) - } - - let response = Grpc_Testing_SimpleResponse.with { - $0.payload = .with { - $0.body = Data(count: Int(request.message.responseSize)) - } - } - - return ServerResponse.Stream { writer in - while self.working.load(ordering: .relaxed) { - try await writer.write(response) - } - return [:] - } - } - - /// Two-sided unbounded streaming between server to client. - /// Both sides send the content of their own choice to the other. - func streamingBothWays( - request: ServerRequest.Stream, - context: ServerContext - ) async throws -> ServerResponse.Stream { - // The 100 size is used by the other implementations as well. - // We are using the same canned response size for all responses - // as it is allowed by the spec. - let response = Grpc_Testing_SimpleResponse.with { - $0.payload = .with { - $0.body = Data(count: 100) - } - } - - final class InboundStreamingSignal: Sendable { - private let _isStreaming: Atomic - - init() { - self._isStreaming = Atomic(true) - } - - var isStreaming: Bool { - self._isStreaming.load(ordering: .relaxed) - } - - func stop() { - self._isStreaming.store(false, ordering: .relaxed) - } - } - - // Marks if the inbound streaming is ongoing or finished. - let inbound = InboundStreamingSignal() - - return ServerResponse.Stream { writer in - try await withThrowingTaskGroup(of: Void.self) { group in - group.addTask { - for try await message in request.messages { - if message.responseStatus.isInitialized { - try self.checkOkStatus(message.responseStatus) - } - } - inbound.stop() - } - - group.addTask { - while inbound.isStreaming && self.working.load(ordering: .acquiring) { - try await writer.write(response) - } - } - - try await group.next() - group.cancelAll() - return [:] - } - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension BenchmarkService { - private func checkOkStatus(_ responseStatus: Grpc_Testing_EchoStatus) throws { - guard let code = Status.Code(rawValue: Int(responseStatus.code)) else { - throw RPCError(code: .invalidArgument, message: "The response status code is invalid.") - } - if let code = RPCError.Code(code) { - throw RPCError(code: code, message: responseStatus.message) - } - } -} diff --git a/Sources/performance-worker/Generated/grpc_core_stats.pb.swift b/Sources/performance-worker/Generated/grpc_core_stats.pb.swift deleted file mode 100644 index e68cf193f..000000000 --- a/Sources/performance-worker/Generated/grpc_core_stats.pb.swift +++ /dev/null @@ -1,286 +0,0 @@ -// DO NOT EDIT. -// swift-format-ignore-file -// swiftlint:disable all -// -// Generated by the Swift generator plugin for the protocol buffer compiler. -// Source: grpc/core/stats.proto -// -// For information on using the generated types, please see the documentation: -// https://github.com/apple/swift-protobuf/ - -// Copyright 2017 gRPC authors. -// -// 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 - -// If the compiler emits an error on this type, it is because this file -// was generated by a version of the `protoc` Swift plug-in that is -// incompatible with the version of SwiftProtobuf to which you are linking. -// Please ensure that you are building against the same version of the API -// that was used to generate this file. -fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { - struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} - typealias Version = _2 -} - -struct Grpc_Core_Bucket: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var start: Double = 0 - - var count: UInt64 = 0 - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -struct Grpc_Core_Histogram: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var buckets: [Grpc_Core_Bucket] = [] - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -struct Grpc_Core_Metric: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var name: String = String() - - var value: Grpc_Core_Metric.OneOf_Value? = nil - - var count: UInt64 { - get { - if case .count(let v)? = value {return v} - return 0 - } - set {value = .count(newValue)} - } - - var histogram: Grpc_Core_Histogram { - get { - if case .histogram(let v)? = value {return v} - return Grpc_Core_Histogram() - } - set {value = .histogram(newValue)} - } - - var unknownFields = SwiftProtobuf.UnknownStorage() - - enum OneOf_Value: Equatable, Sendable { - case count(UInt64) - case histogram(Grpc_Core_Histogram) - - } - - init() {} -} - -struct Grpc_Core_Stats: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var metrics: [Grpc_Core_Metric] = [] - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -// MARK: - Code below here is support for the SwiftProtobuf runtime. - -fileprivate let _protobuf_package = "grpc.core" - -extension Grpc_Core_Bucket: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".Bucket" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "start"), - 2: .same(proto: "count"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularDoubleField(value: &self.start) }() - case 2: try { try decoder.decodeSingularUInt64Field(value: &self.count) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if self.start.bitPattern != 0 { - try visitor.visitSingularDoubleField(value: self.start, fieldNumber: 1) - } - if self.count != 0 { - try visitor.visitSingularUInt64Field(value: self.count, fieldNumber: 2) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Core_Bucket, rhs: Grpc_Core_Bucket) -> Bool { - if lhs.start != rhs.start {return false} - if lhs.count != rhs.count {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Core_Histogram: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".Histogram" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "buckets"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeRepeatedMessageField(value: &self.buckets) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if !self.buckets.isEmpty { - try visitor.visitRepeatedMessageField(value: self.buckets, fieldNumber: 1) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Core_Histogram, rhs: Grpc_Core_Histogram) -> Bool { - if lhs.buckets != rhs.buckets {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Core_Metric: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".Metric" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "name"), - 10: .same(proto: "count"), - 11: .same(proto: "histogram"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularStringField(value: &self.name) }() - case 10: try { - var v: UInt64? - try decoder.decodeSingularUInt64Field(value: &v) - if let v = v { - if self.value != nil {try decoder.handleConflictingOneOf()} - self.value = .count(v) - } - }() - case 11: try { - var v: Grpc_Core_Histogram? - var hadOneofValue = false - if let current = self.value { - hadOneofValue = true - if case .histogram(let m) = current {v = m} - } - try decoder.decodeSingularMessageField(value: &v) - if let v = v { - if hadOneofValue {try decoder.handleConflictingOneOf()} - self.value = .histogram(v) - } - }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 - if !self.name.isEmpty { - try visitor.visitSingularStringField(value: self.name, fieldNumber: 1) - } - switch self.value { - case .count?: try { - guard case .count(let v)? = self.value else { preconditionFailure() } - try visitor.visitSingularUInt64Field(value: v, fieldNumber: 10) - }() - case .histogram?: try { - guard case .histogram(let v)? = self.value else { preconditionFailure() } - try visitor.visitSingularMessageField(value: v, fieldNumber: 11) - }() - case nil: break - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Core_Metric, rhs: Grpc_Core_Metric) -> Bool { - if lhs.name != rhs.name {return false} - if lhs.value != rhs.value {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Core_Stats: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".Stats" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "metrics"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeRepeatedMessageField(value: &self.metrics) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if !self.metrics.isEmpty { - try visitor.visitRepeatedMessageField(value: self.metrics, fieldNumber: 1) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Core_Stats, rhs: Grpc_Core_Stats) -> Bool { - if lhs.metrics != rhs.metrics {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} diff --git a/Sources/performance-worker/Generated/grpc_testing_benchmark_service.grpc.swift b/Sources/performance-worker/Generated/grpc_testing_benchmark_service.grpc.swift deleted file mode 100644 index d8b4cdc6b..000000000 --- a/Sources/performance-worker/Generated/grpc_testing_benchmark_service.grpc.swift +++ /dev/null @@ -1,617 +0,0 @@ -// Copyright 2015 gRPC authors. -// -// 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. - -/// An integration test service that covers all the method signature permutations -/// of unary/streaming requests/responses. - -// DO NOT EDIT. -// swift-format-ignore-file -// -// Generated by the gRPC Swift generator plugin for the protocol buffer compiler. -// Source: grpc/testing/benchmark_service.proto -// -// For information on using the generated types, please see the documentation: -// https://github.com/grpc/grpc-swift - -import GRPCCore -import GRPCProtobuf - -internal enum Grpc_Testing_BenchmarkService { - internal static let descriptor = GRPCCore.ServiceDescriptor.grpc_testing_BenchmarkService - internal enum Method { - internal enum UnaryCall { - internal typealias Input = Grpc_Testing_SimpleRequest - internal typealias Output = Grpc_Testing_SimpleResponse - internal static let descriptor = GRPCCore.MethodDescriptor( - service: Grpc_Testing_BenchmarkService.descriptor.fullyQualifiedService, - method: "UnaryCall" - ) - } - internal enum StreamingCall { - internal typealias Input = Grpc_Testing_SimpleRequest - internal typealias Output = Grpc_Testing_SimpleResponse - internal static let descriptor = GRPCCore.MethodDescriptor( - service: Grpc_Testing_BenchmarkService.descriptor.fullyQualifiedService, - method: "StreamingCall" - ) - } - internal enum StreamingFromClient { - internal typealias Input = Grpc_Testing_SimpleRequest - internal typealias Output = Grpc_Testing_SimpleResponse - internal static let descriptor = GRPCCore.MethodDescriptor( - service: Grpc_Testing_BenchmarkService.descriptor.fullyQualifiedService, - method: "StreamingFromClient" - ) - } - internal enum StreamingFromServer { - internal typealias Input = Grpc_Testing_SimpleRequest - internal typealias Output = Grpc_Testing_SimpleResponse - internal static let descriptor = GRPCCore.MethodDescriptor( - service: Grpc_Testing_BenchmarkService.descriptor.fullyQualifiedService, - method: "StreamingFromServer" - ) - } - internal enum StreamingBothWays { - internal typealias Input = Grpc_Testing_SimpleRequest - internal typealias Output = Grpc_Testing_SimpleResponse - internal static let descriptor = GRPCCore.MethodDescriptor( - service: Grpc_Testing_BenchmarkService.descriptor.fullyQualifiedService, - method: "StreamingBothWays" - ) - } - internal static let descriptors: [GRPCCore.MethodDescriptor] = [ - UnaryCall.descriptor, - StreamingCall.descriptor, - StreamingFromClient.descriptor, - StreamingFromServer.descriptor, - StreamingBothWays.descriptor - ] - } - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - internal typealias StreamingServiceProtocol = Grpc_Testing_BenchmarkServiceStreamingServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - internal typealias ServiceProtocol = Grpc_Testing_BenchmarkServiceServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - internal typealias ClientProtocol = Grpc_Testing_BenchmarkServiceClientProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - internal typealias Client = Grpc_Testing_BenchmarkServiceClient -} - -extension GRPCCore.ServiceDescriptor { - internal static let grpc_testing_BenchmarkService = Self( - package: "grpc.testing", - service: "BenchmarkService" - ) -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -internal protocol Grpc_Testing_BenchmarkServiceStreamingServiceProtocol: GRPCCore.RegistrableRPCService { - /// One request followed by one response. - /// The server returns the client payload as-is. - func unaryCall( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream - - /// Repeated sequence of one request followed by one response. - /// Should be called streaming ping-pong - /// The server returns the client payload as-is on each response - func streamingCall( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream - - /// Single-sided unbounded streaming from client to server - /// The server returns the client payload as-is once the client does WritesDone - func streamingFromClient( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream - - /// Single-sided unbounded streaming from server to client - /// The server repeatedly returns the client payload as-is - func streamingFromServer( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream - - /// Two-sided unbounded streaming between server to client - /// Both sides send the content of their own choice to the other - func streamingBothWays( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream -} - -/// Conformance to `GRPCCore.RegistrableRPCService`. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension Grpc_Testing_BenchmarkService.StreamingServiceProtocol { - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - internal func registerMethods(with router: inout GRPCCore.RPCRouter) { - router.registerHandler( - forMethod: Grpc_Testing_BenchmarkService.Method.UnaryCall.descriptor, - deserializer: GRPCProtobuf.ProtobufDeserializer(), - serializer: GRPCProtobuf.ProtobufSerializer(), - handler: { request, context in - try await self.unaryCall( - request: request, - context: context - ) - } - ) - router.registerHandler( - forMethod: Grpc_Testing_BenchmarkService.Method.StreamingCall.descriptor, - deserializer: GRPCProtobuf.ProtobufDeserializer(), - serializer: GRPCProtobuf.ProtobufSerializer(), - handler: { request, context in - try await self.streamingCall( - request: request, - context: context - ) - } - ) - router.registerHandler( - forMethod: Grpc_Testing_BenchmarkService.Method.StreamingFromClient.descriptor, - deserializer: GRPCProtobuf.ProtobufDeserializer(), - serializer: GRPCProtobuf.ProtobufSerializer(), - handler: { request, context in - try await self.streamingFromClient( - request: request, - context: context - ) - } - ) - router.registerHandler( - forMethod: Grpc_Testing_BenchmarkService.Method.StreamingFromServer.descriptor, - deserializer: GRPCProtobuf.ProtobufDeserializer(), - serializer: GRPCProtobuf.ProtobufSerializer(), - handler: { request, context in - try await self.streamingFromServer( - request: request, - context: context - ) - } - ) - router.registerHandler( - forMethod: Grpc_Testing_BenchmarkService.Method.StreamingBothWays.descriptor, - deserializer: GRPCProtobuf.ProtobufDeserializer(), - serializer: GRPCProtobuf.ProtobufSerializer(), - handler: { request, context in - try await self.streamingBothWays( - request: request, - context: context - ) - } - ) - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -internal protocol Grpc_Testing_BenchmarkServiceServiceProtocol: Grpc_Testing_BenchmarkService.StreamingServiceProtocol { - /// One request followed by one response. - /// The server returns the client payload as-is. - func unaryCall( - request: GRPCCore.ServerRequest.Single, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Single - - /// Repeated sequence of one request followed by one response. - /// Should be called streaming ping-pong - /// The server returns the client payload as-is on each response - func streamingCall( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream - - /// Single-sided unbounded streaming from client to server - /// The server returns the client payload as-is once the client does WritesDone - func streamingFromClient( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Single - - /// Single-sided unbounded streaming from server to client - /// The server repeatedly returns the client payload as-is - func streamingFromServer( - request: GRPCCore.ServerRequest.Single, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream - - /// Two-sided unbounded streaming between server to client - /// Both sides send the content of their own choice to the other - func streamingBothWays( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream -} - -/// Partial conformance to `Grpc_Testing_BenchmarkServiceStreamingServiceProtocol`. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension Grpc_Testing_BenchmarkService.ServiceProtocol { - internal func unaryCall( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream { - let response = try await self.unaryCall( - request: GRPCCore.ServerRequest.Single(stream: request), - context: context - ) - return GRPCCore.ServerResponse.Stream(single: response) - } - - internal func streamingFromClient( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream { - let response = try await self.streamingFromClient( - request: request, - context: context - ) - return GRPCCore.ServerResponse.Stream(single: response) - } - - internal func streamingFromServer( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream { - let response = try await self.streamingFromServer( - request: GRPCCore.ServerRequest.Single(stream: request), - context: context - ) - return response - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -internal protocol Grpc_Testing_BenchmarkServiceClientProtocol: Sendable { - /// One request followed by one response. - /// The server returns the client payload as-is. - func unaryCall( - request: GRPCCore.ClientRequest.Single, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R - ) async throws -> R where R: Sendable - - /// Repeated sequence of one request followed by one response. - /// Should be called streaming ping-pong - /// The server returns the client payload as-is on each response - func streamingCall( - request: GRPCCore.ClientRequest.Stream, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Stream) async throws -> R - ) async throws -> R where R: Sendable - - /// Single-sided unbounded streaming from client to server - /// The server returns the client payload as-is once the client does WritesDone - func streamingFromClient( - request: GRPCCore.ClientRequest.Stream, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R - ) async throws -> R where R: Sendable - - /// Single-sided unbounded streaming from server to client - /// The server repeatedly returns the client payload as-is - func streamingFromServer( - request: GRPCCore.ClientRequest.Single, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Stream) async throws -> R - ) async throws -> R where R: Sendable - - /// Two-sided unbounded streaming between server to client - /// Both sides send the content of their own choice to the other - func streamingBothWays( - request: GRPCCore.ClientRequest.Stream, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Stream) async throws -> R - ) async throws -> R where R: Sendable -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension Grpc_Testing_BenchmarkService.ClientProtocol { - internal func unaryCall( - request: GRPCCore.ClientRequest.Single, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R = { - try $0.message - } - ) async throws -> R where R: Sendable { - try await self.unaryCall( - request: request, - serializer: GRPCProtobuf.ProtobufSerializer(), - deserializer: GRPCProtobuf.ProtobufDeserializer(), - options: options, - body - ) - } - - internal func streamingCall( - request: GRPCCore.ClientRequest.Stream, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Stream) async throws -> R - ) async throws -> R where R: Sendable { - try await self.streamingCall( - request: request, - serializer: GRPCProtobuf.ProtobufSerializer(), - deserializer: GRPCProtobuf.ProtobufDeserializer(), - options: options, - body - ) - } - - internal func streamingFromClient( - request: GRPCCore.ClientRequest.Stream, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R = { - try $0.message - } - ) async throws -> R where R: Sendable { - try await self.streamingFromClient( - request: request, - serializer: GRPCProtobuf.ProtobufSerializer(), - deserializer: GRPCProtobuf.ProtobufDeserializer(), - options: options, - body - ) - } - - internal func streamingFromServer( - request: GRPCCore.ClientRequest.Single, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Stream) async throws -> R - ) async throws -> R where R: Sendable { - try await self.streamingFromServer( - request: request, - serializer: GRPCProtobuf.ProtobufSerializer(), - deserializer: GRPCProtobuf.ProtobufDeserializer(), - options: options, - body - ) - } - - internal func streamingBothWays( - request: GRPCCore.ClientRequest.Stream, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Stream) async throws -> R - ) async throws -> R where R: Sendable { - try await self.streamingBothWays( - request: request, - serializer: GRPCProtobuf.ProtobufSerializer(), - deserializer: GRPCProtobuf.ProtobufDeserializer(), - options: options, - body - ) - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension Grpc_Testing_BenchmarkService.ClientProtocol { - /// One request followed by one response. - /// The server returns the client payload as-is. - internal func unaryCall( - _ message: Grpc_Testing_SimpleRequest, - metadata: GRPCCore.Metadata = [:], - options: GRPCCore.CallOptions = .defaults, - onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> Result = { - try $0.message - } - ) async throws -> Result where Result: Sendable { - let request = GRPCCore.ClientRequest.Single( - message: message, - metadata: metadata - ) - return try await self.unaryCall( - request: request, - options: options, - handleResponse - ) - } - - /// Repeated sequence of one request followed by one response. - /// Should be called streaming ping-pong - /// The server returns the client payload as-is on each response - internal func streamingCall( - metadata: GRPCCore.Metadata = [:], - options: GRPCCore.CallOptions = .defaults, - requestProducer: @Sendable @escaping (GRPCCore.RPCWriter) async throws -> Void, - onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse.Stream) async throws -> Result - ) async throws -> Result where Result: Sendable { - let request = GRPCCore.ClientRequest.Stream( - metadata: metadata, - producer: requestProducer - ) - return try await self.streamingCall( - request: request, - options: options, - handleResponse - ) - } - - /// Single-sided unbounded streaming from client to server - /// The server returns the client payload as-is once the client does WritesDone - internal func streamingFromClient( - metadata: GRPCCore.Metadata = [:], - options: GRPCCore.CallOptions = .defaults, - requestProducer: @Sendable @escaping (GRPCCore.RPCWriter) async throws -> Void, - onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> Result = { - try $0.message - } - ) async throws -> Result where Result: Sendable { - let request = GRPCCore.ClientRequest.Stream( - metadata: metadata, - producer: requestProducer - ) - return try await self.streamingFromClient( - request: request, - options: options, - handleResponse - ) - } - - /// Single-sided unbounded streaming from server to client - /// The server repeatedly returns the client payload as-is - internal func streamingFromServer( - _ message: Grpc_Testing_SimpleRequest, - metadata: GRPCCore.Metadata = [:], - options: GRPCCore.CallOptions = .defaults, - onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse.Stream) async throws -> Result - ) async throws -> Result where Result: Sendable { - let request = GRPCCore.ClientRequest.Single( - message: message, - metadata: metadata - ) - return try await self.streamingFromServer( - request: request, - options: options, - handleResponse - ) - } - - /// Two-sided unbounded streaming between server to client - /// Both sides send the content of their own choice to the other - internal func streamingBothWays( - metadata: GRPCCore.Metadata = [:], - options: GRPCCore.CallOptions = .defaults, - requestProducer: @Sendable @escaping (GRPCCore.RPCWriter) async throws -> Void, - onResponse handleResponse: @Sendable @escaping (GRPCCore.ClientResponse.Stream) async throws -> Result - ) async throws -> Result where Result: Sendable { - let request = GRPCCore.ClientRequest.Stream( - metadata: metadata, - producer: requestProducer - ) - return try await self.streamingBothWays( - request: request, - options: options, - handleResponse - ) - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -internal struct Grpc_Testing_BenchmarkServiceClient: Grpc_Testing_BenchmarkService.ClientProtocol { - private let client: GRPCCore.GRPCClient - - internal init(wrapping client: GRPCCore.GRPCClient) { - self.client = client - } - - /// One request followed by one response. - /// The server returns the client payload as-is. - internal func unaryCall( - request: GRPCCore.ClientRequest.Single, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R = { - try $0.message - } - ) async throws -> R where R: Sendable { - try await self.client.unary( - request: request, - descriptor: Grpc_Testing_BenchmarkService.Method.UnaryCall.descriptor, - serializer: serializer, - deserializer: deserializer, - options: options, - handler: body - ) - } - - /// Repeated sequence of one request followed by one response. - /// Should be called streaming ping-pong - /// The server returns the client payload as-is on each response - internal func streamingCall( - request: GRPCCore.ClientRequest.Stream, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Stream) async throws -> R - ) async throws -> R where R: Sendable { - try await self.client.bidirectionalStreaming( - request: request, - descriptor: Grpc_Testing_BenchmarkService.Method.StreamingCall.descriptor, - serializer: serializer, - deserializer: deserializer, - options: options, - handler: body - ) - } - - /// Single-sided unbounded streaming from client to server - /// The server returns the client payload as-is once the client does WritesDone - internal func streamingFromClient( - request: GRPCCore.ClientRequest.Stream, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Single) async throws -> R = { - try $0.message - } - ) async throws -> R where R: Sendable { - try await self.client.clientStreaming( - request: request, - descriptor: Grpc_Testing_BenchmarkService.Method.StreamingFromClient.descriptor, - serializer: serializer, - deserializer: deserializer, - options: options, - handler: body - ) - } - - /// Single-sided unbounded streaming from server to client - /// The server repeatedly returns the client payload as-is - internal func streamingFromServer( - request: GRPCCore.ClientRequest.Single, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Stream) async throws -> R - ) async throws -> R where R: Sendable { - try await self.client.serverStreaming( - request: request, - descriptor: Grpc_Testing_BenchmarkService.Method.StreamingFromServer.descriptor, - serializer: serializer, - deserializer: deserializer, - options: options, - handler: body - ) - } - - /// Two-sided unbounded streaming between server to client - /// Both sides send the content of their own choice to the other - internal func streamingBothWays( - request: GRPCCore.ClientRequest.Stream, - serializer: some GRPCCore.MessageSerializer, - deserializer: some GRPCCore.MessageDeserializer, - options: GRPCCore.CallOptions = .defaults, - _ body: @Sendable @escaping (GRPCCore.ClientResponse.Stream) async throws -> R - ) async throws -> R where R: Sendable { - try await self.client.bidirectionalStreaming( - request: request, - descriptor: Grpc_Testing_BenchmarkService.Method.StreamingBothWays.descriptor, - serializer: serializer, - deserializer: deserializer, - options: options, - handler: body - ) - } -} \ No newline at end of file diff --git a/Sources/performance-worker/Generated/grpc_testing_benchmark_service.pb.swift b/Sources/performance-worker/Generated/grpc_testing_benchmark_service.pb.swift deleted file mode 100644 index 268a0f868..000000000 --- a/Sources/performance-worker/Generated/grpc_testing_benchmark_service.pb.swift +++ /dev/null @@ -1,28 +0,0 @@ -// DO NOT EDIT. -// swift-format-ignore-file -// swiftlint:disable all -// -// Generated by the Swift generator plugin for the protocol buffer compiler. -// Source: grpc/testing/benchmark_service.proto -// -// For information on using the generated types, please see the documentation: -// https://github.com/apple/swift-protobuf/ - -// Copyright 2015 gRPC authors. -// -// 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. - -/// An integration test service that covers all the method signature permutations -/// of unary/streaming requests/responses. - -// This file contained no messages, enums, or extensions. diff --git a/Sources/performance-worker/Generated/grpc_testing_control.pb.swift b/Sources/performance-worker/Generated/grpc_testing_control.pb.swift deleted file mode 100644 index 777fff519..000000000 --- a/Sources/performance-worker/Generated/grpc_testing_control.pb.swift +++ /dev/null @@ -1,2325 +0,0 @@ -// DO NOT EDIT. -// swift-format-ignore-file -// swiftlint:disable all -// -// Generated by the Swift generator plugin for the protocol buffer compiler. -// Source: grpc/testing/control.proto -// -// For information on using the generated types, please see the documentation: -// https://github.com/apple/swift-protobuf/ - -// Copyright 2015 gRPC authors. -// -// 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 - -// If the compiler emits an error on this type, it is because this file -// was generated by a version of the `protoc` Swift plug-in that is -// incompatible with the version of SwiftProtobuf to which you are linking. -// Please ensure that you are building against the same version of the API -// that was used to generate this file. -fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { - struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} - typealias Version = _2 -} - -enum Grpc_Testing_ClientType: SwiftProtobuf.Enum, Swift.CaseIterable { - typealias RawValue = Int - - /// Many languages support a basic distinction between using - /// sync or async client, and this allows the specification - case syncClient // = 0 - case asyncClient // = 1 - - /// used for some language-specific variants - case otherClient // = 2 - case callbackClient // = 3 - case UNRECOGNIZED(Int) - - init() { - self = .syncClient - } - - init?(rawValue: Int) { - switch rawValue { - case 0: self = .syncClient - case 1: self = .asyncClient - case 2: self = .otherClient - case 3: self = .callbackClient - default: self = .UNRECOGNIZED(rawValue) - } - } - - var rawValue: Int { - switch self { - case .syncClient: return 0 - case .asyncClient: return 1 - case .otherClient: return 2 - case .callbackClient: return 3 - case .UNRECOGNIZED(let i): return i - } - } - - // The compiler won't synthesize support with the UNRECOGNIZED case. - static let allCases: [Grpc_Testing_ClientType] = [ - .syncClient, - .asyncClient, - .otherClient, - .callbackClient, - ] - -} - -enum Grpc_Testing_ServerType: SwiftProtobuf.Enum, Swift.CaseIterable { - typealias RawValue = Int - case syncServer // = 0 - case asyncServer // = 1 - case asyncGenericServer // = 2 - - /// used for some language-specific variants - case otherServer // = 3 - case callbackServer // = 4 - case UNRECOGNIZED(Int) - - init() { - self = .syncServer - } - - init?(rawValue: Int) { - switch rawValue { - case 0: self = .syncServer - case 1: self = .asyncServer - case 2: self = .asyncGenericServer - case 3: self = .otherServer - case 4: self = .callbackServer - default: self = .UNRECOGNIZED(rawValue) - } - } - - var rawValue: Int { - switch self { - case .syncServer: return 0 - case .asyncServer: return 1 - case .asyncGenericServer: return 2 - case .otherServer: return 3 - case .callbackServer: return 4 - case .UNRECOGNIZED(let i): return i - } - } - - // The compiler won't synthesize support with the UNRECOGNIZED case. - static let allCases: [Grpc_Testing_ServerType] = [ - .syncServer, - .asyncServer, - .asyncGenericServer, - .otherServer, - .callbackServer, - ] - -} - -enum Grpc_Testing_RpcType: SwiftProtobuf.Enum, Swift.CaseIterable { - typealias RawValue = Int - case unary // = 0 - case streaming // = 1 - case streamingFromClient // = 2 - case streamingFromServer // = 3 - case streamingBothWays // = 4 - case UNRECOGNIZED(Int) - - init() { - self = .unary - } - - init?(rawValue: Int) { - switch rawValue { - case 0: self = .unary - case 1: self = .streaming - case 2: self = .streamingFromClient - case 3: self = .streamingFromServer - case 4: self = .streamingBothWays - default: self = .UNRECOGNIZED(rawValue) - } - } - - var rawValue: Int { - switch self { - case .unary: return 0 - case .streaming: return 1 - case .streamingFromClient: return 2 - case .streamingFromServer: return 3 - case .streamingBothWays: return 4 - case .UNRECOGNIZED(let i): return i - } - } - - // The compiler won't synthesize support with the UNRECOGNIZED case. - static let allCases: [Grpc_Testing_RpcType] = [ - .unary, - .streaming, - .streamingFromClient, - .streamingFromServer, - .streamingBothWays, - ] - -} - -/// Parameters of poisson process distribution, which is a good representation -/// of activity coming in from independent identical stationary sources. -struct Grpc_Testing_PoissonParams: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// The rate of arrivals (a.k.a. lambda parameter of the exp distribution). - var offeredLoad: Double = 0 - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -/// Once an RPC finishes, immediately start a new one. -/// No configuration parameters needed. -struct Grpc_Testing_ClosedLoopParams: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -struct Grpc_Testing_LoadParams: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var load: Grpc_Testing_LoadParams.OneOf_Load? = nil - - var closedLoop: Grpc_Testing_ClosedLoopParams { - get { - if case .closedLoop(let v)? = load {return v} - return Grpc_Testing_ClosedLoopParams() - } - set {load = .closedLoop(newValue)} - } - - var poisson: Grpc_Testing_PoissonParams { - get { - if case .poisson(let v)? = load {return v} - return Grpc_Testing_PoissonParams() - } - set {load = .poisson(newValue)} - } - - var unknownFields = SwiftProtobuf.UnknownStorage() - - enum OneOf_Load: Equatable, Sendable { - case closedLoop(Grpc_Testing_ClosedLoopParams) - case poisson(Grpc_Testing_PoissonParams) - - } - - init() {} -} - -/// presence of SecurityParams implies use of TLS -struct Grpc_Testing_SecurityParams: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var useTestCa: Bool = false - - var serverHostOverride: String = String() - - var credType: String = String() - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -struct Grpc_Testing_ChannelArg: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var name: String = String() - - var value: Grpc_Testing_ChannelArg.OneOf_Value? = nil - - var strValue: String { - get { - if case .strValue(let v)? = value {return v} - return String() - } - set {value = .strValue(newValue)} - } - - var intValue: Int32 { - get { - if case .intValue(let v)? = value {return v} - return 0 - } - set {value = .intValue(newValue)} - } - - var unknownFields = SwiftProtobuf.UnknownStorage() - - enum OneOf_Value: Equatable, Sendable { - case strValue(String) - case intValue(Int32) - - } - - init() {} -} - -struct Grpc_Testing_ClientConfig: @unchecked Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// List of targets to connect to. At least one target needs to be specified. - var serverTargets: [String] { - get {return _storage._serverTargets} - set {_uniqueStorage()._serverTargets = newValue} - } - - var clientType: Grpc_Testing_ClientType { - get {return _storage._clientType} - set {_uniqueStorage()._clientType = newValue} - } - - var securityParams: Grpc_Testing_SecurityParams { - get {return _storage._securityParams ?? Grpc_Testing_SecurityParams()} - set {_uniqueStorage()._securityParams = newValue} - } - /// Returns true if `securityParams` has been explicitly set. - var hasSecurityParams: Bool {return _storage._securityParams != nil} - /// Clears the value of `securityParams`. Subsequent reads from it will return its default value. - mutating func clearSecurityParams() {_uniqueStorage()._securityParams = nil} - - /// How many concurrent RPCs to start for each channel. - /// For synchronous client, use a separate thread for each outstanding RPC. - var outstandingRpcsPerChannel: Int32 { - get {return _storage._outstandingRpcsPerChannel} - set {_uniqueStorage()._outstandingRpcsPerChannel = newValue} - } - - /// Number of independent client channels to create. - /// i-th channel will connect to server_target[i % server_targets.size()] - var clientChannels: Int32 { - get {return _storage._clientChannels} - set {_uniqueStorage()._clientChannels = newValue} - } - - /// Only for async client. Number of threads to use to start/manage RPCs. - var asyncClientThreads: Int32 { - get {return _storage._asyncClientThreads} - set {_uniqueStorage()._asyncClientThreads = newValue} - } - - var rpcType: Grpc_Testing_RpcType { - get {return _storage._rpcType} - set {_uniqueStorage()._rpcType = newValue} - } - - /// The requested load for the entire client (aggregated over all the threads). - var loadParams: Grpc_Testing_LoadParams { - get {return _storage._loadParams ?? Grpc_Testing_LoadParams()} - set {_uniqueStorage()._loadParams = newValue} - } - /// Returns true if `loadParams` has been explicitly set. - var hasLoadParams: Bool {return _storage._loadParams != nil} - /// Clears the value of `loadParams`. Subsequent reads from it will return its default value. - mutating func clearLoadParams() {_uniqueStorage()._loadParams = nil} - - var payloadConfig: Grpc_Testing_PayloadConfig { - get {return _storage._payloadConfig ?? Grpc_Testing_PayloadConfig()} - set {_uniqueStorage()._payloadConfig = newValue} - } - /// Returns true if `payloadConfig` has been explicitly set. - var hasPayloadConfig: Bool {return _storage._payloadConfig != nil} - /// Clears the value of `payloadConfig`. Subsequent reads from it will return its default value. - mutating func clearPayloadConfig() {_uniqueStorage()._payloadConfig = nil} - - var histogramParams: Grpc_Testing_HistogramParams { - get {return _storage._histogramParams ?? Grpc_Testing_HistogramParams()} - set {_uniqueStorage()._histogramParams = newValue} - } - /// Returns true if `histogramParams` has been explicitly set. - var hasHistogramParams: Bool {return _storage._histogramParams != nil} - /// Clears the value of `histogramParams`. Subsequent reads from it will return its default value. - mutating func clearHistogramParams() {_uniqueStorage()._histogramParams = nil} - - /// Specify the cores we should run the client on, if desired - var coreList: [Int32] { - get {return _storage._coreList} - set {_uniqueStorage()._coreList = newValue} - } - - var coreLimit: Int32 { - get {return _storage._coreLimit} - set {_uniqueStorage()._coreLimit = newValue} - } - - /// If we use an OTHER_CLIENT client_type, this string gives more detail - var otherClientApi: String { - get {return _storage._otherClientApi} - set {_uniqueStorage()._otherClientApi = newValue} - } - - var channelArgs: [Grpc_Testing_ChannelArg] { - get {return _storage._channelArgs} - set {_uniqueStorage()._channelArgs = newValue} - } - - /// Number of threads that share each completion queue - var threadsPerCq: Int32 { - get {return _storage._threadsPerCq} - set {_uniqueStorage()._threadsPerCq = newValue} - } - - /// Number of messages on a stream before it gets finished/restarted - var messagesPerStream: Int32 { - get {return _storage._messagesPerStream} - set {_uniqueStorage()._messagesPerStream = newValue} - } - - /// Use coalescing API when possible. - var useCoalesceApi: Bool { - get {return _storage._useCoalesceApi} - set {_uniqueStorage()._useCoalesceApi = newValue} - } - - /// If 0, disabled. Else, specifies the period between gathering latency - /// medians in milliseconds. - var medianLatencyCollectionIntervalMillis: Int32 { - get {return _storage._medianLatencyCollectionIntervalMillis} - set {_uniqueStorage()._medianLatencyCollectionIntervalMillis = newValue} - } - - /// Number of client processes. 0 indicates no restriction. - var clientProcesses: Int32 { - get {return _storage._clientProcesses} - set {_uniqueStorage()._clientProcesses = newValue} - } - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _storage = _StorageClass.defaultInstance -} - -struct Grpc_Testing_ClientStatus: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var stats: Grpc_Testing_ClientStats { - get {return _stats ?? Grpc_Testing_ClientStats()} - set {_stats = newValue} - } - /// Returns true if `stats` has been explicitly set. - var hasStats: Bool {return self._stats != nil} - /// Clears the value of `stats`. Subsequent reads from it will return its default value. - mutating func clearStats() {self._stats = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _stats: Grpc_Testing_ClientStats? = nil -} - -/// Request current stats -struct Grpc_Testing_Mark: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// if true, the stats will be reset after taking their snapshot. - var reset: Bool = false - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -struct Grpc_Testing_ClientArgs: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var argtype: Grpc_Testing_ClientArgs.OneOf_Argtype? = nil - - var setup: Grpc_Testing_ClientConfig { - get { - if case .setup(let v)? = argtype {return v} - return Grpc_Testing_ClientConfig() - } - set {argtype = .setup(newValue)} - } - - var mark: Grpc_Testing_Mark { - get { - if case .mark(let v)? = argtype {return v} - return Grpc_Testing_Mark() - } - set {argtype = .mark(newValue)} - } - - var unknownFields = SwiftProtobuf.UnknownStorage() - - enum OneOf_Argtype: Equatable, Sendable { - case setup(Grpc_Testing_ClientConfig) - case mark(Grpc_Testing_Mark) - - } - - init() {} -} - -struct Grpc_Testing_ServerConfig: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var serverType: Grpc_Testing_ServerType = .syncServer - - var securityParams: Grpc_Testing_SecurityParams { - get {return _securityParams ?? Grpc_Testing_SecurityParams()} - set {_securityParams = newValue} - } - /// Returns true if `securityParams` has been explicitly set. - var hasSecurityParams: Bool {return self._securityParams != nil} - /// Clears the value of `securityParams`. Subsequent reads from it will return its default value. - mutating func clearSecurityParams() {self._securityParams = nil} - - /// Port on which to listen. Zero means pick unused port. - var port: Int32 = 0 - - /// Only for async server. Number of threads used to serve the requests. - var asyncServerThreads: Int32 = 0 - - /// Specify the number of cores to limit server to, if desired - var coreLimit: Int32 = 0 - - /// payload config, used in generic server. - /// Note this must NOT be used in proto (non-generic) servers. For proto servers, - /// 'response sizes' must be configured from the 'response_size' field of the - /// 'SimpleRequest' objects in RPC requests. - var payloadConfig: Grpc_Testing_PayloadConfig { - get {return _payloadConfig ?? Grpc_Testing_PayloadConfig()} - set {_payloadConfig = newValue} - } - /// Returns true if `payloadConfig` has been explicitly set. - var hasPayloadConfig: Bool {return self._payloadConfig != nil} - /// Clears the value of `payloadConfig`. Subsequent reads from it will return its default value. - mutating func clearPayloadConfig() {self._payloadConfig = nil} - - /// Specify the cores we should run the server on, if desired - var coreList: [Int32] = [] - - /// If we use an OTHER_SERVER client_type, this string gives more detail - var otherServerApi: String = String() - - /// Number of threads that share each completion queue - var threadsPerCq: Int32 = 0 - - /// Buffer pool size (no buffer pool specified if unset) - var resourceQuotaSize: Int32 = 0 - - var channelArgs: [Grpc_Testing_ChannelArg] = [] - - /// Number of server processes. 0 indicates no restriction. - var serverProcesses: Int32 = 0 - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _securityParams: Grpc_Testing_SecurityParams? = nil - fileprivate var _payloadConfig: Grpc_Testing_PayloadConfig? = nil -} - -struct Grpc_Testing_ServerArgs: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var argtype: Grpc_Testing_ServerArgs.OneOf_Argtype? = nil - - var setup: Grpc_Testing_ServerConfig { - get { - if case .setup(let v)? = argtype {return v} - return Grpc_Testing_ServerConfig() - } - set {argtype = .setup(newValue)} - } - - var mark: Grpc_Testing_Mark { - get { - if case .mark(let v)? = argtype {return v} - return Grpc_Testing_Mark() - } - set {argtype = .mark(newValue)} - } - - var unknownFields = SwiftProtobuf.UnknownStorage() - - enum OneOf_Argtype: Equatable, Sendable { - case setup(Grpc_Testing_ServerConfig) - case mark(Grpc_Testing_Mark) - - } - - init() {} -} - -struct Grpc_Testing_ServerStatus: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var stats: Grpc_Testing_ServerStats { - get {return _stats ?? Grpc_Testing_ServerStats()} - set {_stats = newValue} - } - /// Returns true if `stats` has been explicitly set. - var hasStats: Bool {return self._stats != nil} - /// Clears the value of `stats`. Subsequent reads from it will return its default value. - mutating func clearStats() {self._stats = nil} - - /// the port bound by the server - var port: Int32 = 0 - - /// Number of cores available to the server - var cores: Int32 = 0 - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _stats: Grpc_Testing_ServerStats? = nil -} - -struct Grpc_Testing_CoreRequest: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -struct Grpc_Testing_CoreResponse: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// Number of cores available on the server - var cores: Int32 = 0 - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -struct Grpc_Testing_Void: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -/// A single performance scenario: input to qps_json_driver -struct Grpc_Testing_Scenario: @unchecked Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// Human readable name for this scenario - var name: String { - get {return _storage._name} - set {_uniqueStorage()._name = newValue} - } - - /// Client configuration - var clientConfig: Grpc_Testing_ClientConfig { - get {return _storage._clientConfig ?? Grpc_Testing_ClientConfig()} - set {_uniqueStorage()._clientConfig = newValue} - } - /// Returns true if `clientConfig` has been explicitly set. - var hasClientConfig: Bool {return _storage._clientConfig != nil} - /// Clears the value of `clientConfig`. Subsequent reads from it will return its default value. - mutating func clearClientConfig() {_uniqueStorage()._clientConfig = nil} - - /// Number of clients to start for the test - var numClients: Int32 { - get {return _storage._numClients} - set {_uniqueStorage()._numClients = newValue} - } - - /// Server configuration - var serverConfig: Grpc_Testing_ServerConfig { - get {return _storage._serverConfig ?? Grpc_Testing_ServerConfig()} - set {_uniqueStorage()._serverConfig = newValue} - } - /// Returns true if `serverConfig` has been explicitly set. - var hasServerConfig: Bool {return _storage._serverConfig != nil} - /// Clears the value of `serverConfig`. Subsequent reads from it will return its default value. - mutating func clearServerConfig() {_uniqueStorage()._serverConfig = nil} - - /// Number of servers to start for the test - var numServers: Int32 { - get {return _storage._numServers} - set {_uniqueStorage()._numServers = newValue} - } - - /// Warmup period, in seconds - var warmupSeconds: Int32 { - get {return _storage._warmupSeconds} - set {_uniqueStorage()._warmupSeconds = newValue} - } - - /// Benchmark time, in seconds - var benchmarkSeconds: Int32 { - get {return _storage._benchmarkSeconds} - set {_uniqueStorage()._benchmarkSeconds = newValue} - } - - /// Number of workers to spawn locally (usually zero) - var spawnLocalWorkerCount: Int32 { - get {return _storage._spawnLocalWorkerCount} - set {_uniqueStorage()._spawnLocalWorkerCount = newValue} - } - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _storage = _StorageClass.defaultInstance -} - -/// A set of scenarios to be run with qps_json_driver -struct Grpc_Testing_Scenarios: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var scenarios: [Grpc_Testing_Scenario] = [] - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -/// Basic summary that can be computed from ClientStats and ServerStats -/// once the scenario has finished. -struct Grpc_Testing_ScenarioResultSummary: @unchecked Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// Total number of operations per second over all clients. What is counted as 1 'operation' depends on the benchmark scenarios: - /// For unary benchmarks, an operation is processing of a single unary RPC. - /// For streaming benchmarks, an operation is processing of a single ping pong of request and response. - var qps: Double { - get {return _storage._qps} - set {_uniqueStorage()._qps = newValue} - } - - /// QPS per server core. - var qpsPerServerCore: Double { - get {return _storage._qpsPerServerCore} - set {_uniqueStorage()._qpsPerServerCore = newValue} - } - - /// The total server cpu load based on system time across all server processes, expressed as percentage of a single cpu core. - /// For example, 85 implies 85% of a cpu core, 125 implies 125% of a cpu core. Since we are accumulating the cpu load across all the server - /// processes, the value could > 100 when there are multiple servers or a single server using multiple threads and cores. - /// Same explanation for the total client cpu load below. - var serverSystemTime: Double { - get {return _storage._serverSystemTime} - set {_uniqueStorage()._serverSystemTime = newValue} - } - - /// The total server cpu load based on user time across all server processes, expressed as percentage of a single cpu core. (85 => 85%, 125 => 125%) - var serverUserTime: Double { - get {return _storage._serverUserTime} - set {_uniqueStorage()._serverUserTime = newValue} - } - - /// The total client cpu load based on system time across all client processes, expressed as percentage of a single cpu core. (85 => 85%, 125 => 125%) - var clientSystemTime: Double { - get {return _storage._clientSystemTime} - set {_uniqueStorage()._clientSystemTime = newValue} - } - - /// The total client cpu load based on user time across all client processes, expressed as percentage of a single cpu core. (85 => 85%, 125 => 125%) - var clientUserTime: Double { - get {return _storage._clientUserTime} - set {_uniqueStorage()._clientUserTime = newValue} - } - - /// X% latency percentiles (in nanoseconds) - var latency50: Double { - get {return _storage._latency50} - set {_uniqueStorage()._latency50 = newValue} - } - - var latency90: Double { - get {return _storage._latency90} - set {_uniqueStorage()._latency90 = newValue} - } - - var latency95: Double { - get {return _storage._latency95} - set {_uniqueStorage()._latency95 = newValue} - } - - var latency99: Double { - get {return _storage._latency99} - set {_uniqueStorage()._latency99 = newValue} - } - - var latency999: Double { - get {return _storage._latency999} - set {_uniqueStorage()._latency999 = newValue} - } - - /// server cpu usage percentage - var serverCpuUsage: Double { - get {return _storage._serverCpuUsage} - set {_uniqueStorage()._serverCpuUsage = newValue} - } - - /// Number of requests that succeeded/failed - var successfulRequestsPerSecond: Double { - get {return _storage._successfulRequestsPerSecond} - set {_uniqueStorage()._successfulRequestsPerSecond = newValue} - } - - var failedRequestsPerSecond: Double { - get {return _storage._failedRequestsPerSecond} - set {_uniqueStorage()._failedRequestsPerSecond = newValue} - } - - /// Number of polls called inside completion queue per request - var clientPollsPerRequest: Double { - get {return _storage._clientPollsPerRequest} - set {_uniqueStorage()._clientPollsPerRequest = newValue} - } - - var serverPollsPerRequest: Double { - get {return _storage._serverPollsPerRequest} - set {_uniqueStorage()._serverPollsPerRequest = newValue} - } - - /// Queries per CPU-sec over all servers or clients - var serverQueriesPerCpuSec: Double { - get {return _storage._serverQueriesPerCpuSec} - set {_uniqueStorage()._serverQueriesPerCpuSec = newValue} - } - - var clientQueriesPerCpuSec: Double { - get {return _storage._clientQueriesPerCpuSec} - set {_uniqueStorage()._clientQueriesPerCpuSec = newValue} - } - - /// Start and end time for the test scenario - var startTime: SwiftProtobuf.Google_Protobuf_Timestamp { - get {return _storage._startTime ?? SwiftProtobuf.Google_Protobuf_Timestamp()} - set {_uniqueStorage()._startTime = newValue} - } - /// Returns true if `startTime` has been explicitly set. - var hasStartTime: Bool {return _storage._startTime != nil} - /// Clears the value of `startTime`. Subsequent reads from it will return its default value. - mutating func clearStartTime() {_uniqueStorage()._startTime = nil} - - var endTime: SwiftProtobuf.Google_Protobuf_Timestamp { - get {return _storage._endTime ?? SwiftProtobuf.Google_Protobuf_Timestamp()} - set {_uniqueStorage()._endTime = newValue} - } - /// Returns true if `endTime` has been explicitly set. - var hasEndTime: Bool {return _storage._endTime != nil} - /// Clears the value of `endTime`. Subsequent reads from it will return its default value. - mutating func clearEndTime() {_uniqueStorage()._endTime = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _storage = _StorageClass.defaultInstance -} - -/// Results of a single benchmark scenario. -struct Grpc_Testing_ScenarioResult: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// Inputs used to run the scenario. - var scenario: Grpc_Testing_Scenario { - get {return _scenario ?? Grpc_Testing_Scenario()} - set {_scenario = newValue} - } - /// Returns true if `scenario` has been explicitly set. - var hasScenario: Bool {return self._scenario != nil} - /// Clears the value of `scenario`. Subsequent reads from it will return its default value. - mutating func clearScenario() {self._scenario = nil} - - /// Histograms from all clients merged into one histogram. - var latencies: Grpc_Testing_HistogramData { - get {return _latencies ?? Grpc_Testing_HistogramData()} - set {_latencies = newValue} - } - /// Returns true if `latencies` has been explicitly set. - var hasLatencies: Bool {return self._latencies != nil} - /// Clears the value of `latencies`. Subsequent reads from it will return its default value. - mutating func clearLatencies() {self._latencies = nil} - - /// Client stats for each client - var clientStats: [Grpc_Testing_ClientStats] = [] - - /// Server stats for each server - var serverStats: [Grpc_Testing_ServerStats] = [] - - /// Number of cores available to each server - var serverCores: [Int32] = [] - - /// An after-the-fact computed summary - var summary: Grpc_Testing_ScenarioResultSummary { - get {return _summary ?? Grpc_Testing_ScenarioResultSummary()} - set {_summary = newValue} - } - /// Returns true if `summary` has been explicitly set. - var hasSummary: Bool {return self._summary != nil} - /// Clears the value of `summary`. Subsequent reads from it will return its default value. - mutating func clearSummary() {self._summary = nil} - - /// Information on success or failure of each worker - var clientSuccess: [Bool] = [] - - var serverSuccess: [Bool] = [] - - /// Number of failed requests (one row per status code seen) - var requestResults: [Grpc_Testing_RequestResultCount] = [] - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _scenario: Grpc_Testing_Scenario? = nil - fileprivate var _latencies: Grpc_Testing_HistogramData? = nil - fileprivate var _summary: Grpc_Testing_ScenarioResultSummary? = nil -} - -// MARK: - Code below here is support for the SwiftProtobuf runtime. - -fileprivate let _protobuf_package = "grpc.testing" - -extension Grpc_Testing_ClientType: SwiftProtobuf._ProtoNameProviding { - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 0: .same(proto: "SYNC_CLIENT"), - 1: .same(proto: "ASYNC_CLIENT"), - 2: .same(proto: "OTHER_CLIENT"), - 3: .same(proto: "CALLBACK_CLIENT"), - ] -} - -extension Grpc_Testing_ServerType: SwiftProtobuf._ProtoNameProviding { - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 0: .same(proto: "SYNC_SERVER"), - 1: .same(proto: "ASYNC_SERVER"), - 2: .same(proto: "ASYNC_GENERIC_SERVER"), - 3: .same(proto: "OTHER_SERVER"), - 4: .same(proto: "CALLBACK_SERVER"), - ] -} - -extension Grpc_Testing_RpcType: SwiftProtobuf._ProtoNameProviding { - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 0: .same(proto: "UNARY"), - 1: .same(proto: "STREAMING"), - 2: .same(proto: "STREAMING_FROM_CLIENT"), - 3: .same(proto: "STREAMING_FROM_SERVER"), - 4: .same(proto: "STREAMING_BOTH_WAYS"), - ] -} - -extension Grpc_Testing_PoissonParams: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".PoissonParams" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "offered_load"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularDoubleField(value: &self.offeredLoad) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if self.offeredLoad.bitPattern != 0 { - try visitor.visitSingularDoubleField(value: self.offeredLoad, fieldNumber: 1) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_PoissonParams, rhs: Grpc_Testing_PoissonParams) -> Bool { - if lhs.offeredLoad != rhs.offeredLoad {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_ClosedLoopParams: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".ClosedLoopParams" - static let _protobuf_nameMap = SwiftProtobuf._NameMap() - - mutating func decodeMessage(decoder: inout D) throws { - // Load everything into unknown fields - while try decoder.nextFieldNumber() != nil {} - } - - func traverse(visitor: inout V) throws { - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_ClosedLoopParams, rhs: Grpc_Testing_ClosedLoopParams) -> Bool { - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_LoadParams: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".LoadParams" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "closed_loop"), - 2: .same(proto: "poisson"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { - var v: Grpc_Testing_ClosedLoopParams? - var hadOneofValue = false - if let current = self.load { - hadOneofValue = true - if case .closedLoop(let m) = current {v = m} - } - try decoder.decodeSingularMessageField(value: &v) - if let v = v { - if hadOneofValue {try decoder.handleConflictingOneOf()} - self.load = .closedLoop(v) - } - }() - case 2: try { - var v: Grpc_Testing_PoissonParams? - var hadOneofValue = false - if let current = self.load { - hadOneofValue = true - if case .poisson(let m) = current {v = m} - } - try decoder.decodeSingularMessageField(value: &v) - if let v = v { - if hadOneofValue {try decoder.handleConflictingOneOf()} - self.load = .poisson(v) - } - }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 - switch self.load { - case .closedLoop?: try { - guard case .closedLoop(let v)? = self.load else { preconditionFailure() } - try visitor.visitSingularMessageField(value: v, fieldNumber: 1) - }() - case .poisson?: try { - guard case .poisson(let v)? = self.load else { preconditionFailure() } - try visitor.visitSingularMessageField(value: v, fieldNumber: 2) - }() - case nil: break - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_LoadParams, rhs: Grpc_Testing_LoadParams) -> Bool { - if lhs.load != rhs.load {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_SecurityParams: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".SecurityParams" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "use_test_ca"), - 2: .standard(proto: "server_host_override"), - 3: .standard(proto: "cred_type"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularBoolField(value: &self.useTestCa) }() - case 2: try { try decoder.decodeSingularStringField(value: &self.serverHostOverride) }() - case 3: try { try decoder.decodeSingularStringField(value: &self.credType) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if self.useTestCa != false { - try visitor.visitSingularBoolField(value: self.useTestCa, fieldNumber: 1) - } - if !self.serverHostOverride.isEmpty { - try visitor.visitSingularStringField(value: self.serverHostOverride, fieldNumber: 2) - } - if !self.credType.isEmpty { - try visitor.visitSingularStringField(value: self.credType, fieldNumber: 3) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_SecurityParams, rhs: Grpc_Testing_SecurityParams) -> Bool { - if lhs.useTestCa != rhs.useTestCa {return false} - if lhs.serverHostOverride != rhs.serverHostOverride {return false} - if lhs.credType != rhs.credType {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_ChannelArg: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".ChannelArg" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "name"), - 2: .standard(proto: "str_value"), - 3: .standard(proto: "int_value"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularStringField(value: &self.name) }() - case 2: try { - var v: String? - try decoder.decodeSingularStringField(value: &v) - if let v = v { - if self.value != nil {try decoder.handleConflictingOneOf()} - self.value = .strValue(v) - } - }() - case 3: try { - var v: Int32? - try decoder.decodeSingularInt32Field(value: &v) - if let v = v { - if self.value != nil {try decoder.handleConflictingOneOf()} - self.value = .intValue(v) - } - }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 - if !self.name.isEmpty { - try visitor.visitSingularStringField(value: self.name, fieldNumber: 1) - } - switch self.value { - case .strValue?: try { - guard case .strValue(let v)? = self.value else { preconditionFailure() } - try visitor.visitSingularStringField(value: v, fieldNumber: 2) - }() - case .intValue?: try { - guard case .intValue(let v)? = self.value else { preconditionFailure() } - try visitor.visitSingularInt32Field(value: v, fieldNumber: 3) - }() - case nil: break - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_ChannelArg, rhs: Grpc_Testing_ChannelArg) -> Bool { - if lhs.name != rhs.name {return false} - if lhs.value != rhs.value {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_ClientConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".ClientConfig" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "server_targets"), - 2: .standard(proto: "client_type"), - 3: .standard(proto: "security_params"), - 4: .standard(proto: "outstanding_rpcs_per_channel"), - 5: .standard(proto: "client_channels"), - 7: .standard(proto: "async_client_threads"), - 8: .standard(proto: "rpc_type"), - 10: .standard(proto: "load_params"), - 11: .standard(proto: "payload_config"), - 12: .standard(proto: "histogram_params"), - 13: .standard(proto: "core_list"), - 14: .standard(proto: "core_limit"), - 15: .standard(proto: "other_client_api"), - 16: .standard(proto: "channel_args"), - 17: .standard(proto: "threads_per_cq"), - 18: .standard(proto: "messages_per_stream"), - 19: .standard(proto: "use_coalesce_api"), - 20: .standard(proto: "median_latency_collection_interval_millis"), - 21: .standard(proto: "client_processes"), - ] - - fileprivate class _StorageClass { - var _serverTargets: [String] = [] - var _clientType: Grpc_Testing_ClientType = .syncClient - var _securityParams: Grpc_Testing_SecurityParams? = nil - var _outstandingRpcsPerChannel: Int32 = 0 - var _clientChannels: Int32 = 0 - var _asyncClientThreads: Int32 = 0 - var _rpcType: Grpc_Testing_RpcType = .unary - var _loadParams: Grpc_Testing_LoadParams? = nil - var _payloadConfig: Grpc_Testing_PayloadConfig? = nil - var _histogramParams: Grpc_Testing_HistogramParams? = nil - var _coreList: [Int32] = [] - var _coreLimit: Int32 = 0 - var _otherClientApi: String = String() - var _channelArgs: [Grpc_Testing_ChannelArg] = [] - var _threadsPerCq: Int32 = 0 - var _messagesPerStream: Int32 = 0 - var _useCoalesceApi: Bool = false - var _medianLatencyCollectionIntervalMillis: Int32 = 0 - var _clientProcesses: Int32 = 0 - - #if swift(>=5.10) - // This property is used as the initial default value for new instances of the type. - // The type itself is protecting the reference to its storage via CoW semantics. - // This will force a copy to be made of this reference when the first mutation occurs; - // hence, it is safe to mark this as `nonisolated(unsafe)`. - static nonisolated(unsafe) let defaultInstance = _StorageClass() - #else - static let defaultInstance = _StorageClass() - #endif - - private init() {} - - init(copying source: _StorageClass) { - _serverTargets = source._serverTargets - _clientType = source._clientType - _securityParams = source._securityParams - _outstandingRpcsPerChannel = source._outstandingRpcsPerChannel - _clientChannels = source._clientChannels - _asyncClientThreads = source._asyncClientThreads - _rpcType = source._rpcType - _loadParams = source._loadParams - _payloadConfig = source._payloadConfig - _histogramParams = source._histogramParams - _coreList = source._coreList - _coreLimit = source._coreLimit - _otherClientApi = source._otherClientApi - _channelArgs = source._channelArgs - _threadsPerCq = source._threadsPerCq - _messagesPerStream = source._messagesPerStream - _useCoalesceApi = source._useCoalesceApi - _medianLatencyCollectionIntervalMillis = source._medianLatencyCollectionIntervalMillis - _clientProcesses = source._clientProcesses - } - } - - fileprivate mutating func _uniqueStorage() -> _StorageClass { - if !isKnownUniquelyReferenced(&_storage) { - _storage = _StorageClass(copying: _storage) - } - return _storage - } - - mutating func decodeMessage(decoder: inout D) throws { - _ = _uniqueStorage() - try withExtendedLifetime(_storage) { (_storage: _StorageClass) in - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeRepeatedStringField(value: &_storage._serverTargets) }() - case 2: try { try decoder.decodeSingularEnumField(value: &_storage._clientType) }() - case 3: try { try decoder.decodeSingularMessageField(value: &_storage._securityParams) }() - case 4: try { try decoder.decodeSingularInt32Field(value: &_storage._outstandingRpcsPerChannel) }() - case 5: try { try decoder.decodeSingularInt32Field(value: &_storage._clientChannels) }() - case 7: try { try decoder.decodeSingularInt32Field(value: &_storage._asyncClientThreads) }() - case 8: try { try decoder.decodeSingularEnumField(value: &_storage._rpcType) }() - case 10: try { try decoder.decodeSingularMessageField(value: &_storage._loadParams) }() - case 11: try { try decoder.decodeSingularMessageField(value: &_storage._payloadConfig) }() - case 12: try { try decoder.decodeSingularMessageField(value: &_storage._histogramParams) }() - case 13: try { try decoder.decodeRepeatedInt32Field(value: &_storage._coreList) }() - case 14: try { try decoder.decodeSingularInt32Field(value: &_storage._coreLimit) }() - case 15: try { try decoder.decodeSingularStringField(value: &_storage._otherClientApi) }() - case 16: try { try decoder.decodeRepeatedMessageField(value: &_storage._channelArgs) }() - case 17: try { try decoder.decodeSingularInt32Field(value: &_storage._threadsPerCq) }() - case 18: try { try decoder.decodeSingularInt32Field(value: &_storage._messagesPerStream) }() - case 19: try { try decoder.decodeSingularBoolField(value: &_storage._useCoalesceApi) }() - case 20: try { try decoder.decodeSingularInt32Field(value: &_storage._medianLatencyCollectionIntervalMillis) }() - case 21: try { try decoder.decodeSingularInt32Field(value: &_storage._clientProcesses) }() - default: break - } - } - } - } - - func traverse(visitor: inout V) throws { - try withExtendedLifetime(_storage) { (_storage: _StorageClass) in - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 - if !_storage._serverTargets.isEmpty { - try visitor.visitRepeatedStringField(value: _storage._serverTargets, fieldNumber: 1) - } - if _storage._clientType != .syncClient { - try visitor.visitSingularEnumField(value: _storage._clientType, fieldNumber: 2) - } - try { if let v = _storage._securityParams { - try visitor.visitSingularMessageField(value: v, fieldNumber: 3) - } }() - if _storage._outstandingRpcsPerChannel != 0 { - try visitor.visitSingularInt32Field(value: _storage._outstandingRpcsPerChannel, fieldNumber: 4) - } - if _storage._clientChannels != 0 { - try visitor.visitSingularInt32Field(value: _storage._clientChannels, fieldNumber: 5) - } - if _storage._asyncClientThreads != 0 { - try visitor.visitSingularInt32Field(value: _storage._asyncClientThreads, fieldNumber: 7) - } - if _storage._rpcType != .unary { - try visitor.visitSingularEnumField(value: _storage._rpcType, fieldNumber: 8) - } - try { if let v = _storage._loadParams { - try visitor.visitSingularMessageField(value: v, fieldNumber: 10) - } }() - try { if let v = _storage._payloadConfig { - try visitor.visitSingularMessageField(value: v, fieldNumber: 11) - } }() - try { if let v = _storage._histogramParams { - try visitor.visitSingularMessageField(value: v, fieldNumber: 12) - } }() - if !_storage._coreList.isEmpty { - try visitor.visitPackedInt32Field(value: _storage._coreList, fieldNumber: 13) - } - if _storage._coreLimit != 0 { - try visitor.visitSingularInt32Field(value: _storage._coreLimit, fieldNumber: 14) - } - if !_storage._otherClientApi.isEmpty { - try visitor.visitSingularStringField(value: _storage._otherClientApi, fieldNumber: 15) - } - if !_storage._channelArgs.isEmpty { - try visitor.visitRepeatedMessageField(value: _storage._channelArgs, fieldNumber: 16) - } - if _storage._threadsPerCq != 0 { - try visitor.visitSingularInt32Field(value: _storage._threadsPerCq, fieldNumber: 17) - } - if _storage._messagesPerStream != 0 { - try visitor.visitSingularInt32Field(value: _storage._messagesPerStream, fieldNumber: 18) - } - if _storage._useCoalesceApi != false { - try visitor.visitSingularBoolField(value: _storage._useCoalesceApi, fieldNumber: 19) - } - if _storage._medianLatencyCollectionIntervalMillis != 0 { - try visitor.visitSingularInt32Field(value: _storage._medianLatencyCollectionIntervalMillis, fieldNumber: 20) - } - if _storage._clientProcesses != 0 { - try visitor.visitSingularInt32Field(value: _storage._clientProcesses, fieldNumber: 21) - } - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_ClientConfig, rhs: Grpc_Testing_ClientConfig) -> Bool { - if lhs._storage !== rhs._storage { - let storagesAreEqual: Bool = withExtendedLifetime((lhs._storage, rhs._storage)) { (_args: (_StorageClass, _StorageClass)) in - let _storage = _args.0 - let rhs_storage = _args.1 - if _storage._serverTargets != rhs_storage._serverTargets {return false} - if _storage._clientType != rhs_storage._clientType {return false} - if _storage._securityParams != rhs_storage._securityParams {return false} - if _storage._outstandingRpcsPerChannel != rhs_storage._outstandingRpcsPerChannel {return false} - if _storage._clientChannels != rhs_storage._clientChannels {return false} - if _storage._asyncClientThreads != rhs_storage._asyncClientThreads {return false} - if _storage._rpcType != rhs_storage._rpcType {return false} - if _storage._loadParams != rhs_storage._loadParams {return false} - if _storage._payloadConfig != rhs_storage._payloadConfig {return false} - if _storage._histogramParams != rhs_storage._histogramParams {return false} - if _storage._coreList != rhs_storage._coreList {return false} - if _storage._coreLimit != rhs_storage._coreLimit {return false} - if _storage._otherClientApi != rhs_storage._otherClientApi {return false} - if _storage._channelArgs != rhs_storage._channelArgs {return false} - if _storage._threadsPerCq != rhs_storage._threadsPerCq {return false} - if _storage._messagesPerStream != rhs_storage._messagesPerStream {return false} - if _storage._useCoalesceApi != rhs_storage._useCoalesceApi {return false} - if _storage._medianLatencyCollectionIntervalMillis != rhs_storage._medianLatencyCollectionIntervalMillis {return false} - if _storage._clientProcesses != rhs_storage._clientProcesses {return false} - return true - } - if !storagesAreEqual {return false} - } - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_ClientStatus: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".ClientStatus" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "stats"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularMessageField(value: &self._stats) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 - try { if let v = self._stats { - try visitor.visitSingularMessageField(value: v, fieldNumber: 1) - } }() - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_ClientStatus, rhs: Grpc_Testing_ClientStatus) -> Bool { - if lhs._stats != rhs._stats {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_Mark: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".Mark" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "reset"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularBoolField(value: &self.reset) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if self.reset != false { - try visitor.visitSingularBoolField(value: self.reset, fieldNumber: 1) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_Mark, rhs: Grpc_Testing_Mark) -> Bool { - if lhs.reset != rhs.reset {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_ClientArgs: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".ClientArgs" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "setup"), - 2: .same(proto: "mark"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { - var v: Grpc_Testing_ClientConfig? - var hadOneofValue = false - if let current = self.argtype { - hadOneofValue = true - if case .setup(let m) = current {v = m} - } - try decoder.decodeSingularMessageField(value: &v) - if let v = v { - if hadOneofValue {try decoder.handleConflictingOneOf()} - self.argtype = .setup(v) - } - }() - case 2: try { - var v: Grpc_Testing_Mark? - var hadOneofValue = false - if let current = self.argtype { - hadOneofValue = true - if case .mark(let m) = current {v = m} - } - try decoder.decodeSingularMessageField(value: &v) - if let v = v { - if hadOneofValue {try decoder.handleConflictingOneOf()} - self.argtype = .mark(v) - } - }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 - switch self.argtype { - case .setup?: try { - guard case .setup(let v)? = self.argtype else { preconditionFailure() } - try visitor.visitSingularMessageField(value: v, fieldNumber: 1) - }() - case .mark?: try { - guard case .mark(let v)? = self.argtype else { preconditionFailure() } - try visitor.visitSingularMessageField(value: v, fieldNumber: 2) - }() - case nil: break - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_ClientArgs, rhs: Grpc_Testing_ClientArgs) -> Bool { - if lhs.argtype != rhs.argtype {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_ServerConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".ServerConfig" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "server_type"), - 2: .standard(proto: "security_params"), - 4: .same(proto: "port"), - 7: .standard(proto: "async_server_threads"), - 8: .standard(proto: "core_limit"), - 9: .standard(proto: "payload_config"), - 10: .standard(proto: "core_list"), - 11: .standard(proto: "other_server_api"), - 12: .standard(proto: "threads_per_cq"), - 1001: .standard(proto: "resource_quota_size"), - 1002: .standard(proto: "channel_args"), - 21: .standard(proto: "server_processes"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularEnumField(value: &self.serverType) }() - case 2: try { try decoder.decodeSingularMessageField(value: &self._securityParams) }() - case 4: try { try decoder.decodeSingularInt32Field(value: &self.port) }() - case 7: try { try decoder.decodeSingularInt32Field(value: &self.asyncServerThreads) }() - case 8: try { try decoder.decodeSingularInt32Field(value: &self.coreLimit) }() - case 9: try { try decoder.decodeSingularMessageField(value: &self._payloadConfig) }() - case 10: try { try decoder.decodeRepeatedInt32Field(value: &self.coreList) }() - case 11: try { try decoder.decodeSingularStringField(value: &self.otherServerApi) }() - case 12: try { try decoder.decodeSingularInt32Field(value: &self.threadsPerCq) }() - case 21: try { try decoder.decodeSingularInt32Field(value: &self.serverProcesses) }() - case 1001: try { try decoder.decodeSingularInt32Field(value: &self.resourceQuotaSize) }() - case 1002: try { try decoder.decodeRepeatedMessageField(value: &self.channelArgs) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 - if self.serverType != .syncServer { - try visitor.visitSingularEnumField(value: self.serverType, fieldNumber: 1) - } - try { if let v = self._securityParams { - try visitor.visitSingularMessageField(value: v, fieldNumber: 2) - } }() - if self.port != 0 { - try visitor.visitSingularInt32Field(value: self.port, fieldNumber: 4) - } - if self.asyncServerThreads != 0 { - try visitor.visitSingularInt32Field(value: self.asyncServerThreads, fieldNumber: 7) - } - if self.coreLimit != 0 { - try visitor.visitSingularInt32Field(value: self.coreLimit, fieldNumber: 8) - } - try { if let v = self._payloadConfig { - try visitor.visitSingularMessageField(value: v, fieldNumber: 9) - } }() - if !self.coreList.isEmpty { - try visitor.visitPackedInt32Field(value: self.coreList, fieldNumber: 10) - } - if !self.otherServerApi.isEmpty { - try visitor.visitSingularStringField(value: self.otherServerApi, fieldNumber: 11) - } - if self.threadsPerCq != 0 { - try visitor.visitSingularInt32Field(value: self.threadsPerCq, fieldNumber: 12) - } - if self.serverProcesses != 0 { - try visitor.visitSingularInt32Field(value: self.serverProcesses, fieldNumber: 21) - } - if self.resourceQuotaSize != 0 { - try visitor.visitSingularInt32Field(value: self.resourceQuotaSize, fieldNumber: 1001) - } - if !self.channelArgs.isEmpty { - try visitor.visitRepeatedMessageField(value: self.channelArgs, fieldNumber: 1002) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_ServerConfig, rhs: Grpc_Testing_ServerConfig) -> Bool { - if lhs.serverType != rhs.serverType {return false} - if lhs._securityParams != rhs._securityParams {return false} - if lhs.port != rhs.port {return false} - if lhs.asyncServerThreads != rhs.asyncServerThreads {return false} - if lhs.coreLimit != rhs.coreLimit {return false} - if lhs._payloadConfig != rhs._payloadConfig {return false} - if lhs.coreList != rhs.coreList {return false} - if lhs.otherServerApi != rhs.otherServerApi {return false} - if lhs.threadsPerCq != rhs.threadsPerCq {return false} - if lhs.resourceQuotaSize != rhs.resourceQuotaSize {return false} - if lhs.channelArgs != rhs.channelArgs {return false} - if lhs.serverProcesses != rhs.serverProcesses {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_ServerArgs: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".ServerArgs" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "setup"), - 2: .same(proto: "mark"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { - var v: Grpc_Testing_ServerConfig? - var hadOneofValue = false - if let current = self.argtype { - hadOneofValue = true - if case .setup(let m) = current {v = m} - } - try decoder.decodeSingularMessageField(value: &v) - if let v = v { - if hadOneofValue {try decoder.handleConflictingOneOf()} - self.argtype = .setup(v) - } - }() - case 2: try { - var v: Grpc_Testing_Mark? - var hadOneofValue = false - if let current = self.argtype { - hadOneofValue = true - if case .mark(let m) = current {v = m} - } - try decoder.decodeSingularMessageField(value: &v) - if let v = v { - if hadOneofValue {try decoder.handleConflictingOneOf()} - self.argtype = .mark(v) - } - }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 - switch self.argtype { - case .setup?: try { - guard case .setup(let v)? = self.argtype else { preconditionFailure() } - try visitor.visitSingularMessageField(value: v, fieldNumber: 1) - }() - case .mark?: try { - guard case .mark(let v)? = self.argtype else { preconditionFailure() } - try visitor.visitSingularMessageField(value: v, fieldNumber: 2) - }() - case nil: break - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_ServerArgs, rhs: Grpc_Testing_ServerArgs) -> Bool { - if lhs.argtype != rhs.argtype {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_ServerStatus: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".ServerStatus" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "stats"), - 2: .same(proto: "port"), - 3: .same(proto: "cores"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularMessageField(value: &self._stats) }() - case 2: try { try decoder.decodeSingularInt32Field(value: &self.port) }() - case 3: try { try decoder.decodeSingularInt32Field(value: &self.cores) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 - try { if let v = self._stats { - try visitor.visitSingularMessageField(value: v, fieldNumber: 1) - } }() - if self.port != 0 { - try visitor.visitSingularInt32Field(value: self.port, fieldNumber: 2) - } - if self.cores != 0 { - try visitor.visitSingularInt32Field(value: self.cores, fieldNumber: 3) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_ServerStatus, rhs: Grpc_Testing_ServerStatus) -> Bool { - if lhs._stats != rhs._stats {return false} - if lhs.port != rhs.port {return false} - if lhs.cores != rhs.cores {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_CoreRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".CoreRequest" - static let _protobuf_nameMap = SwiftProtobuf._NameMap() - - mutating func decodeMessage(decoder: inout D) throws { - // Load everything into unknown fields - while try decoder.nextFieldNumber() != nil {} - } - - func traverse(visitor: inout V) throws { - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_CoreRequest, rhs: Grpc_Testing_CoreRequest) -> Bool { - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_CoreResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".CoreResponse" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "cores"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularInt32Field(value: &self.cores) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if self.cores != 0 { - try visitor.visitSingularInt32Field(value: self.cores, fieldNumber: 1) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_CoreResponse, rhs: Grpc_Testing_CoreResponse) -> Bool { - if lhs.cores != rhs.cores {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_Void: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".Void" - static let _protobuf_nameMap = SwiftProtobuf._NameMap() - - mutating func decodeMessage(decoder: inout D) throws { - // Load everything into unknown fields - while try decoder.nextFieldNumber() != nil {} - } - - func traverse(visitor: inout V) throws { - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_Void, rhs: Grpc_Testing_Void) -> Bool { - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_Scenario: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".Scenario" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "name"), - 2: .standard(proto: "client_config"), - 3: .standard(proto: "num_clients"), - 4: .standard(proto: "server_config"), - 5: .standard(proto: "num_servers"), - 6: .standard(proto: "warmup_seconds"), - 7: .standard(proto: "benchmark_seconds"), - 8: .standard(proto: "spawn_local_worker_count"), - ] - - fileprivate class _StorageClass { - var _name: String = String() - var _clientConfig: Grpc_Testing_ClientConfig? = nil - var _numClients: Int32 = 0 - var _serverConfig: Grpc_Testing_ServerConfig? = nil - var _numServers: Int32 = 0 - var _warmupSeconds: Int32 = 0 - var _benchmarkSeconds: Int32 = 0 - var _spawnLocalWorkerCount: Int32 = 0 - - #if swift(>=5.10) - // This property is used as the initial default value for new instances of the type. - // The type itself is protecting the reference to its storage via CoW semantics. - // This will force a copy to be made of this reference when the first mutation occurs; - // hence, it is safe to mark this as `nonisolated(unsafe)`. - static nonisolated(unsafe) let defaultInstance = _StorageClass() - #else - static let defaultInstance = _StorageClass() - #endif - - private init() {} - - init(copying source: _StorageClass) { - _name = source._name - _clientConfig = source._clientConfig - _numClients = source._numClients - _serverConfig = source._serverConfig - _numServers = source._numServers - _warmupSeconds = source._warmupSeconds - _benchmarkSeconds = source._benchmarkSeconds - _spawnLocalWorkerCount = source._spawnLocalWorkerCount - } - } - - fileprivate mutating func _uniqueStorage() -> _StorageClass { - if !isKnownUniquelyReferenced(&_storage) { - _storage = _StorageClass(copying: _storage) - } - return _storage - } - - mutating func decodeMessage(decoder: inout D) throws { - _ = _uniqueStorage() - try withExtendedLifetime(_storage) { (_storage: _StorageClass) in - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularStringField(value: &_storage._name) }() - case 2: try { try decoder.decodeSingularMessageField(value: &_storage._clientConfig) }() - case 3: try { try decoder.decodeSingularInt32Field(value: &_storage._numClients) }() - case 4: try { try decoder.decodeSingularMessageField(value: &_storage._serverConfig) }() - case 5: try { try decoder.decodeSingularInt32Field(value: &_storage._numServers) }() - case 6: try { try decoder.decodeSingularInt32Field(value: &_storage._warmupSeconds) }() - case 7: try { try decoder.decodeSingularInt32Field(value: &_storage._benchmarkSeconds) }() - case 8: try { try decoder.decodeSingularInt32Field(value: &_storage._spawnLocalWorkerCount) }() - default: break - } - } - } - } - - func traverse(visitor: inout V) throws { - try withExtendedLifetime(_storage) { (_storage: _StorageClass) in - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 - if !_storage._name.isEmpty { - try visitor.visitSingularStringField(value: _storage._name, fieldNumber: 1) - } - try { if let v = _storage._clientConfig { - try visitor.visitSingularMessageField(value: v, fieldNumber: 2) - } }() - if _storage._numClients != 0 { - try visitor.visitSingularInt32Field(value: _storage._numClients, fieldNumber: 3) - } - try { if let v = _storage._serverConfig { - try visitor.visitSingularMessageField(value: v, fieldNumber: 4) - } }() - if _storage._numServers != 0 { - try visitor.visitSingularInt32Field(value: _storage._numServers, fieldNumber: 5) - } - if _storage._warmupSeconds != 0 { - try visitor.visitSingularInt32Field(value: _storage._warmupSeconds, fieldNumber: 6) - } - if _storage._benchmarkSeconds != 0 { - try visitor.visitSingularInt32Field(value: _storage._benchmarkSeconds, fieldNumber: 7) - } - if _storage._spawnLocalWorkerCount != 0 { - try visitor.visitSingularInt32Field(value: _storage._spawnLocalWorkerCount, fieldNumber: 8) - } - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_Scenario, rhs: Grpc_Testing_Scenario) -> Bool { - if lhs._storage !== rhs._storage { - let storagesAreEqual: Bool = withExtendedLifetime((lhs._storage, rhs._storage)) { (_args: (_StorageClass, _StorageClass)) in - let _storage = _args.0 - let rhs_storage = _args.1 - if _storage._name != rhs_storage._name {return false} - if _storage._clientConfig != rhs_storage._clientConfig {return false} - if _storage._numClients != rhs_storage._numClients {return false} - if _storage._serverConfig != rhs_storage._serverConfig {return false} - if _storage._numServers != rhs_storage._numServers {return false} - if _storage._warmupSeconds != rhs_storage._warmupSeconds {return false} - if _storage._benchmarkSeconds != rhs_storage._benchmarkSeconds {return false} - if _storage._spawnLocalWorkerCount != rhs_storage._spawnLocalWorkerCount {return false} - return true - } - if !storagesAreEqual {return false} - } - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_Scenarios: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".Scenarios" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "scenarios"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeRepeatedMessageField(value: &self.scenarios) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if !self.scenarios.isEmpty { - try visitor.visitRepeatedMessageField(value: self.scenarios, fieldNumber: 1) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_Scenarios, rhs: Grpc_Testing_Scenarios) -> Bool { - if lhs.scenarios != rhs.scenarios {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_ScenarioResultSummary: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".ScenarioResultSummary" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "qps"), - 2: .standard(proto: "qps_per_server_core"), - 3: .standard(proto: "server_system_time"), - 4: .standard(proto: "server_user_time"), - 5: .standard(proto: "client_system_time"), - 6: .standard(proto: "client_user_time"), - 7: .standard(proto: "latency_50"), - 8: .standard(proto: "latency_90"), - 9: .standard(proto: "latency_95"), - 10: .standard(proto: "latency_99"), - 11: .standard(proto: "latency_999"), - 12: .standard(proto: "server_cpu_usage"), - 13: .standard(proto: "successful_requests_per_second"), - 14: .standard(proto: "failed_requests_per_second"), - 15: .standard(proto: "client_polls_per_request"), - 16: .standard(proto: "server_polls_per_request"), - 17: .standard(proto: "server_queries_per_cpu_sec"), - 18: .standard(proto: "client_queries_per_cpu_sec"), - 19: .standard(proto: "start_time"), - 20: .standard(proto: "end_time"), - ] - - fileprivate class _StorageClass { - var _qps: Double = 0 - var _qpsPerServerCore: Double = 0 - var _serverSystemTime: Double = 0 - var _serverUserTime: Double = 0 - var _clientSystemTime: Double = 0 - var _clientUserTime: Double = 0 - var _latency50: Double = 0 - var _latency90: Double = 0 - var _latency95: Double = 0 - var _latency99: Double = 0 - var _latency999: Double = 0 - var _serverCpuUsage: Double = 0 - var _successfulRequestsPerSecond: Double = 0 - var _failedRequestsPerSecond: Double = 0 - var _clientPollsPerRequest: Double = 0 - var _serverPollsPerRequest: Double = 0 - var _serverQueriesPerCpuSec: Double = 0 - var _clientQueriesPerCpuSec: Double = 0 - var _startTime: SwiftProtobuf.Google_Protobuf_Timestamp? = nil - var _endTime: SwiftProtobuf.Google_Protobuf_Timestamp? = nil - - #if swift(>=5.10) - // This property is used as the initial default value for new instances of the type. - // The type itself is protecting the reference to its storage via CoW semantics. - // This will force a copy to be made of this reference when the first mutation occurs; - // hence, it is safe to mark this as `nonisolated(unsafe)`. - static nonisolated(unsafe) let defaultInstance = _StorageClass() - #else - static let defaultInstance = _StorageClass() - #endif - - private init() {} - - init(copying source: _StorageClass) { - _qps = source._qps - _qpsPerServerCore = source._qpsPerServerCore - _serverSystemTime = source._serverSystemTime - _serverUserTime = source._serverUserTime - _clientSystemTime = source._clientSystemTime - _clientUserTime = source._clientUserTime - _latency50 = source._latency50 - _latency90 = source._latency90 - _latency95 = source._latency95 - _latency99 = source._latency99 - _latency999 = source._latency999 - _serverCpuUsage = source._serverCpuUsage - _successfulRequestsPerSecond = source._successfulRequestsPerSecond - _failedRequestsPerSecond = source._failedRequestsPerSecond - _clientPollsPerRequest = source._clientPollsPerRequest - _serverPollsPerRequest = source._serverPollsPerRequest - _serverQueriesPerCpuSec = source._serverQueriesPerCpuSec - _clientQueriesPerCpuSec = source._clientQueriesPerCpuSec - _startTime = source._startTime - _endTime = source._endTime - } - } - - fileprivate mutating func _uniqueStorage() -> _StorageClass { - if !isKnownUniquelyReferenced(&_storage) { - _storage = _StorageClass(copying: _storage) - } - return _storage - } - - mutating func decodeMessage(decoder: inout D) throws { - _ = _uniqueStorage() - try withExtendedLifetime(_storage) { (_storage: _StorageClass) in - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularDoubleField(value: &_storage._qps) }() - case 2: try { try decoder.decodeSingularDoubleField(value: &_storage._qpsPerServerCore) }() - case 3: try { try decoder.decodeSingularDoubleField(value: &_storage._serverSystemTime) }() - case 4: try { try decoder.decodeSingularDoubleField(value: &_storage._serverUserTime) }() - case 5: try { try decoder.decodeSingularDoubleField(value: &_storage._clientSystemTime) }() - case 6: try { try decoder.decodeSingularDoubleField(value: &_storage._clientUserTime) }() - case 7: try { try decoder.decodeSingularDoubleField(value: &_storage._latency50) }() - case 8: try { try decoder.decodeSingularDoubleField(value: &_storage._latency90) }() - case 9: try { try decoder.decodeSingularDoubleField(value: &_storage._latency95) }() - case 10: try { try decoder.decodeSingularDoubleField(value: &_storage._latency99) }() - case 11: try { try decoder.decodeSingularDoubleField(value: &_storage._latency999) }() - case 12: try { try decoder.decodeSingularDoubleField(value: &_storage._serverCpuUsage) }() - case 13: try { try decoder.decodeSingularDoubleField(value: &_storage._successfulRequestsPerSecond) }() - case 14: try { try decoder.decodeSingularDoubleField(value: &_storage._failedRequestsPerSecond) }() - case 15: try { try decoder.decodeSingularDoubleField(value: &_storage._clientPollsPerRequest) }() - case 16: try { try decoder.decodeSingularDoubleField(value: &_storage._serverPollsPerRequest) }() - case 17: try { try decoder.decodeSingularDoubleField(value: &_storage._serverQueriesPerCpuSec) }() - case 18: try { try decoder.decodeSingularDoubleField(value: &_storage._clientQueriesPerCpuSec) }() - case 19: try { try decoder.decodeSingularMessageField(value: &_storage._startTime) }() - case 20: try { try decoder.decodeSingularMessageField(value: &_storage._endTime) }() - default: break - } - } - } - } - - func traverse(visitor: inout V) throws { - try withExtendedLifetime(_storage) { (_storage: _StorageClass) in - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 - if _storage._qps.bitPattern != 0 { - try visitor.visitSingularDoubleField(value: _storage._qps, fieldNumber: 1) - } - if _storage._qpsPerServerCore.bitPattern != 0 { - try visitor.visitSingularDoubleField(value: _storage._qpsPerServerCore, fieldNumber: 2) - } - if _storage._serverSystemTime.bitPattern != 0 { - try visitor.visitSingularDoubleField(value: _storage._serverSystemTime, fieldNumber: 3) - } - if _storage._serverUserTime.bitPattern != 0 { - try visitor.visitSingularDoubleField(value: _storage._serverUserTime, fieldNumber: 4) - } - if _storage._clientSystemTime.bitPattern != 0 { - try visitor.visitSingularDoubleField(value: _storage._clientSystemTime, fieldNumber: 5) - } - if _storage._clientUserTime.bitPattern != 0 { - try visitor.visitSingularDoubleField(value: _storage._clientUserTime, fieldNumber: 6) - } - if _storage._latency50.bitPattern != 0 { - try visitor.visitSingularDoubleField(value: _storage._latency50, fieldNumber: 7) - } - if _storage._latency90.bitPattern != 0 { - try visitor.visitSingularDoubleField(value: _storage._latency90, fieldNumber: 8) - } - if _storage._latency95.bitPattern != 0 { - try visitor.visitSingularDoubleField(value: _storage._latency95, fieldNumber: 9) - } - if _storage._latency99.bitPattern != 0 { - try visitor.visitSingularDoubleField(value: _storage._latency99, fieldNumber: 10) - } - if _storage._latency999.bitPattern != 0 { - try visitor.visitSingularDoubleField(value: _storage._latency999, fieldNumber: 11) - } - if _storage._serverCpuUsage.bitPattern != 0 { - try visitor.visitSingularDoubleField(value: _storage._serverCpuUsage, fieldNumber: 12) - } - if _storage._successfulRequestsPerSecond.bitPattern != 0 { - try visitor.visitSingularDoubleField(value: _storage._successfulRequestsPerSecond, fieldNumber: 13) - } - if _storage._failedRequestsPerSecond.bitPattern != 0 { - try visitor.visitSingularDoubleField(value: _storage._failedRequestsPerSecond, fieldNumber: 14) - } - if _storage._clientPollsPerRequest.bitPattern != 0 { - try visitor.visitSingularDoubleField(value: _storage._clientPollsPerRequest, fieldNumber: 15) - } - if _storage._serverPollsPerRequest.bitPattern != 0 { - try visitor.visitSingularDoubleField(value: _storage._serverPollsPerRequest, fieldNumber: 16) - } - if _storage._serverQueriesPerCpuSec.bitPattern != 0 { - try visitor.visitSingularDoubleField(value: _storage._serverQueriesPerCpuSec, fieldNumber: 17) - } - if _storage._clientQueriesPerCpuSec.bitPattern != 0 { - try visitor.visitSingularDoubleField(value: _storage._clientQueriesPerCpuSec, fieldNumber: 18) - } - try { if let v = _storage._startTime { - try visitor.visitSingularMessageField(value: v, fieldNumber: 19) - } }() - try { if let v = _storage._endTime { - try visitor.visitSingularMessageField(value: v, fieldNumber: 20) - } }() - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_ScenarioResultSummary, rhs: Grpc_Testing_ScenarioResultSummary) -> Bool { - if lhs._storage !== rhs._storage { - let storagesAreEqual: Bool = withExtendedLifetime((lhs._storage, rhs._storage)) { (_args: (_StorageClass, _StorageClass)) in - let _storage = _args.0 - let rhs_storage = _args.1 - if _storage._qps != rhs_storage._qps {return false} - if _storage._qpsPerServerCore != rhs_storage._qpsPerServerCore {return false} - if _storage._serverSystemTime != rhs_storage._serverSystemTime {return false} - if _storage._serverUserTime != rhs_storage._serverUserTime {return false} - if _storage._clientSystemTime != rhs_storage._clientSystemTime {return false} - if _storage._clientUserTime != rhs_storage._clientUserTime {return false} - if _storage._latency50 != rhs_storage._latency50 {return false} - if _storage._latency90 != rhs_storage._latency90 {return false} - if _storage._latency95 != rhs_storage._latency95 {return false} - if _storage._latency99 != rhs_storage._latency99 {return false} - if _storage._latency999 != rhs_storage._latency999 {return false} - if _storage._serverCpuUsage != rhs_storage._serverCpuUsage {return false} - if _storage._successfulRequestsPerSecond != rhs_storage._successfulRequestsPerSecond {return false} - if _storage._failedRequestsPerSecond != rhs_storage._failedRequestsPerSecond {return false} - if _storage._clientPollsPerRequest != rhs_storage._clientPollsPerRequest {return false} - if _storage._serverPollsPerRequest != rhs_storage._serverPollsPerRequest {return false} - if _storage._serverQueriesPerCpuSec != rhs_storage._serverQueriesPerCpuSec {return false} - if _storage._clientQueriesPerCpuSec != rhs_storage._clientQueriesPerCpuSec {return false} - if _storage._startTime != rhs_storage._startTime {return false} - if _storage._endTime != rhs_storage._endTime {return false} - return true - } - if !storagesAreEqual {return false} - } - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_ScenarioResult: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".ScenarioResult" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "scenario"), - 2: .same(proto: "latencies"), - 3: .standard(proto: "client_stats"), - 4: .standard(proto: "server_stats"), - 5: .standard(proto: "server_cores"), - 6: .same(proto: "summary"), - 7: .standard(proto: "client_success"), - 8: .standard(proto: "server_success"), - 9: .standard(proto: "request_results"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularMessageField(value: &self._scenario) }() - case 2: try { try decoder.decodeSingularMessageField(value: &self._latencies) }() - case 3: try { try decoder.decodeRepeatedMessageField(value: &self.clientStats) }() - case 4: try { try decoder.decodeRepeatedMessageField(value: &self.serverStats) }() - case 5: try { try decoder.decodeRepeatedInt32Field(value: &self.serverCores) }() - case 6: try { try decoder.decodeSingularMessageField(value: &self._summary) }() - case 7: try { try decoder.decodeRepeatedBoolField(value: &self.clientSuccess) }() - case 8: try { try decoder.decodeRepeatedBoolField(value: &self.serverSuccess) }() - case 9: try { try decoder.decodeRepeatedMessageField(value: &self.requestResults) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 - try { if let v = self._scenario { - try visitor.visitSingularMessageField(value: v, fieldNumber: 1) - } }() - try { if let v = self._latencies { - try visitor.visitSingularMessageField(value: v, fieldNumber: 2) - } }() - if !self.clientStats.isEmpty { - try visitor.visitRepeatedMessageField(value: self.clientStats, fieldNumber: 3) - } - if !self.serverStats.isEmpty { - try visitor.visitRepeatedMessageField(value: self.serverStats, fieldNumber: 4) - } - if !self.serverCores.isEmpty { - try visitor.visitPackedInt32Field(value: self.serverCores, fieldNumber: 5) - } - try { if let v = self._summary { - try visitor.visitSingularMessageField(value: v, fieldNumber: 6) - } }() - if !self.clientSuccess.isEmpty { - try visitor.visitPackedBoolField(value: self.clientSuccess, fieldNumber: 7) - } - if !self.serverSuccess.isEmpty { - try visitor.visitPackedBoolField(value: self.serverSuccess, fieldNumber: 8) - } - if !self.requestResults.isEmpty { - try visitor.visitRepeatedMessageField(value: self.requestResults, fieldNumber: 9) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_ScenarioResult, rhs: Grpc_Testing_ScenarioResult) -> Bool { - if lhs._scenario != rhs._scenario {return false} - if lhs._latencies != rhs._latencies {return false} - if lhs.clientStats != rhs.clientStats {return false} - if lhs.serverStats != rhs.serverStats {return false} - if lhs.serverCores != rhs.serverCores {return false} - if lhs._summary != rhs._summary {return false} - if lhs.clientSuccess != rhs.clientSuccess {return false} - if lhs.serverSuccess != rhs.serverSuccess {return false} - if lhs.requestResults != rhs.requestResults {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} diff --git a/Sources/performance-worker/Generated/grpc_testing_messages.pb.swift b/Sources/performance-worker/Generated/grpc_testing_messages.pb.swift deleted file mode 100644 index 0665c8f0c..000000000 --- a/Sources/performance-worker/Generated/grpc_testing_messages.pb.swift +++ /dev/null @@ -1,2140 +0,0 @@ -// DO NOT EDIT. -// swift-format-ignore-file -// swiftlint:disable all -// -// Generated by the Swift generator plugin for the protocol buffer compiler. -// Source: grpc/testing/messages.proto -// -// For information on using the generated types, please see the documentation: -// https://github.com/apple/swift-protobuf/ - -// Copyright 2015-2016 gRPC authors. -// -// 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. - -// Message definitions to be used by integration test service definitions. - -import Foundation -import SwiftProtobuf - -// If the compiler emits an error on this type, it is because this file -// was generated by a version of the `protoc` Swift plug-in that is -// incompatible with the version of SwiftProtobuf to which you are linking. -// Please ensure that you are building against the same version of the API -// that was used to generate this file. -fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { - struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} - typealias Version = _2 -} - -/// The type of payload that should be returned. -enum Grpc_Testing_PayloadType: SwiftProtobuf.Enum, Swift.CaseIterable { - typealias RawValue = Int - - /// Compressable text format. - case compressable // = 0 - case UNRECOGNIZED(Int) - - init() { - self = .compressable - } - - init?(rawValue: Int) { - switch rawValue { - case 0: self = .compressable - default: self = .UNRECOGNIZED(rawValue) - } - } - - var rawValue: Int { - switch self { - case .compressable: return 0 - case .UNRECOGNIZED(let i): return i - } - } - - // The compiler won't synthesize support with the UNRECOGNIZED case. - static let allCases: [Grpc_Testing_PayloadType] = [ - .compressable, - ] - -} - -/// The type of route that a client took to reach a server w.r.t. gRPCLB. -/// The server must fill in "fallback" if it detects that the RPC reached -/// the server via the "gRPCLB fallback" path, and "backend" if it detects -/// that the RPC reached the server via "gRPCLB backend" path (i.e. if it got -/// the address of this server from the gRPCLB server BalanceLoad RPC). Exactly -/// how this detection is done is context and server dependent. -enum Grpc_Testing_GrpclbRouteType: SwiftProtobuf.Enum, Swift.CaseIterable { - typealias RawValue = Int - - /// Server didn't detect the route that a client took to reach it. - case unknown // = 0 - - /// Indicates that a client reached a server via gRPCLB fallback. - case fallback // = 1 - - /// Indicates that a client reached a server as a gRPCLB-given backend. - case backend // = 2 - case UNRECOGNIZED(Int) - - init() { - self = .unknown - } - - init?(rawValue: Int) { - switch rawValue { - case 0: self = .unknown - case 1: self = .fallback - case 2: self = .backend - default: self = .UNRECOGNIZED(rawValue) - } - } - - var rawValue: Int { - switch self { - case .unknown: return 0 - case .fallback: return 1 - case .backend: return 2 - case .UNRECOGNIZED(let i): return i - } - } - - // The compiler won't synthesize support with the UNRECOGNIZED case. - static let allCases: [Grpc_Testing_GrpclbRouteType] = [ - .unknown, - .fallback, - .backend, - ] - -} - -/// TODO(dgq): Go back to using well-known types once -/// https://github.com/grpc/grpc/issues/6980 has been fixed. -/// import "google/protobuf/wrappers.proto"; -struct Grpc_Testing_BoolValue: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// The bool value. - var value: Bool = false - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -/// A block of data, to simply increase gRPC message size. -struct Grpc_Testing_Payload: @unchecked Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// The type of data in body. - var type: Grpc_Testing_PayloadType = .compressable - - /// Primary contents of payload. - var body: Data = Data() - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -/// A protobuf representation for grpc status. This is used by test -/// clients to specify a status that the server should attempt to return. -struct Grpc_Testing_EchoStatus: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var code: Int32 = 0 - - var message: String = String() - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -/// Unary request. -struct Grpc_Testing_SimpleRequest: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// Desired payload type in the response from the server. - /// If response_type is RANDOM, server randomly chooses one from other formats. - var responseType: Grpc_Testing_PayloadType = .compressable - - /// Desired payload size in the response from the server. - var responseSize: Int32 = 0 - - /// Optional input payload sent along with the request. - var payload: Grpc_Testing_Payload { - get {return _payload ?? Grpc_Testing_Payload()} - set {_payload = newValue} - } - /// Returns true if `payload` has been explicitly set. - var hasPayload: Bool {return self._payload != nil} - /// Clears the value of `payload`. Subsequent reads from it will return its default value. - mutating func clearPayload() {self._payload = nil} - - /// Whether SimpleResponse should include username. - var fillUsername: Bool = false - - /// Whether SimpleResponse should include OAuth scope. - var fillOauthScope: Bool = false - - /// Whether to request the server to compress the response. This field is - /// "nullable" in order to interoperate seamlessly with clients not able to - /// implement the full compression tests by introspecting the call to verify - /// the response's compression status. - var responseCompressed: Grpc_Testing_BoolValue { - get {return _responseCompressed ?? Grpc_Testing_BoolValue()} - set {_responseCompressed = newValue} - } - /// Returns true if `responseCompressed` has been explicitly set. - var hasResponseCompressed: Bool {return self._responseCompressed != nil} - /// Clears the value of `responseCompressed`. Subsequent reads from it will return its default value. - mutating func clearResponseCompressed() {self._responseCompressed = nil} - - /// Whether server should return a given status - var responseStatus: Grpc_Testing_EchoStatus { - get {return _responseStatus ?? Grpc_Testing_EchoStatus()} - set {_responseStatus = newValue} - } - /// Returns true if `responseStatus` has been explicitly set. - var hasResponseStatus: Bool {return self._responseStatus != nil} - /// Clears the value of `responseStatus`. Subsequent reads from it will return its default value. - mutating func clearResponseStatus() {self._responseStatus = nil} - - /// Whether the server should expect this request to be compressed. - var expectCompressed: Grpc_Testing_BoolValue { - get {return _expectCompressed ?? Grpc_Testing_BoolValue()} - set {_expectCompressed = newValue} - } - /// Returns true if `expectCompressed` has been explicitly set. - var hasExpectCompressed: Bool {return self._expectCompressed != nil} - /// Clears the value of `expectCompressed`. Subsequent reads from it will return its default value. - mutating func clearExpectCompressed() {self._expectCompressed = nil} - - /// Whether SimpleResponse should include server_id. - var fillServerID: Bool = false - - /// Whether SimpleResponse should include grpclb_route_type. - var fillGrpclbRouteType: Bool = false - - /// If set the server should record this metrics report data for the current RPC. - var orcaPerQueryReport: Grpc_Testing_TestOrcaReport { - get {return _orcaPerQueryReport ?? Grpc_Testing_TestOrcaReport()} - set {_orcaPerQueryReport = newValue} - } - /// Returns true if `orcaPerQueryReport` has been explicitly set. - var hasOrcaPerQueryReport: Bool {return self._orcaPerQueryReport != nil} - /// Clears the value of `orcaPerQueryReport`. Subsequent reads from it will return its default value. - mutating func clearOrcaPerQueryReport() {self._orcaPerQueryReport = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _payload: Grpc_Testing_Payload? = nil - fileprivate var _responseCompressed: Grpc_Testing_BoolValue? = nil - fileprivate var _responseStatus: Grpc_Testing_EchoStatus? = nil - fileprivate var _expectCompressed: Grpc_Testing_BoolValue? = nil - fileprivate var _orcaPerQueryReport: Grpc_Testing_TestOrcaReport? = nil -} - -/// Unary response, as configured by the request. -struct Grpc_Testing_SimpleResponse: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// Payload to increase message size. - var payload: Grpc_Testing_Payload { - get {return _payload ?? Grpc_Testing_Payload()} - set {_payload = newValue} - } - /// Returns true if `payload` has been explicitly set. - var hasPayload: Bool {return self._payload != nil} - /// Clears the value of `payload`. Subsequent reads from it will return its default value. - mutating func clearPayload() {self._payload = nil} - - /// The user the request came from, for verifying authentication was - /// successful when the client expected it. - var username: String = String() - - /// OAuth scope. - var oauthScope: String = String() - - /// Server ID. This must be unique among different server instances, - /// but the same across all RPC's made to a particular server instance. - var serverID: String = String() - - /// gRPCLB Path. - var grpclbRouteType: Grpc_Testing_GrpclbRouteType = .unknown - - /// Server hostname. - var hostname: String = String() - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _payload: Grpc_Testing_Payload? = nil -} - -/// Client-streaming request. -struct Grpc_Testing_StreamingInputCallRequest: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// Optional input payload sent along with the request. - var payload: Grpc_Testing_Payload { - get {return _payload ?? Grpc_Testing_Payload()} - set {_payload = newValue} - } - /// Returns true if `payload` has been explicitly set. - var hasPayload: Bool {return self._payload != nil} - /// Clears the value of `payload`. Subsequent reads from it will return its default value. - mutating func clearPayload() {self._payload = nil} - - /// Whether the server should expect this request to be compressed. This field - /// is "nullable" in order to interoperate seamlessly with servers not able to - /// implement the full compression tests by introspecting the call to verify - /// the request's compression status. - var expectCompressed: Grpc_Testing_BoolValue { - get {return _expectCompressed ?? Grpc_Testing_BoolValue()} - set {_expectCompressed = newValue} - } - /// Returns true if `expectCompressed` has been explicitly set. - var hasExpectCompressed: Bool {return self._expectCompressed != nil} - /// Clears the value of `expectCompressed`. Subsequent reads from it will return its default value. - mutating func clearExpectCompressed() {self._expectCompressed = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _payload: Grpc_Testing_Payload? = nil - fileprivate var _expectCompressed: Grpc_Testing_BoolValue? = nil -} - -/// Client-streaming response. -struct Grpc_Testing_StreamingInputCallResponse: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// Aggregated size of payloads received from the client. - var aggregatedPayloadSize: Int32 = 0 - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -/// Configuration for a particular response. -struct Grpc_Testing_ResponseParameters: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// Desired payload sizes in responses from the server. - var size: Int32 = 0 - - /// Desired interval between consecutive responses in the response stream in - /// microseconds. - var intervalUs: Int32 = 0 - - /// Whether to request the server to compress the response. This field is - /// "nullable" in order to interoperate seamlessly with clients not able to - /// implement the full compression tests by introspecting the call to verify - /// the response's compression status. - var compressed: Grpc_Testing_BoolValue { - get {return _compressed ?? Grpc_Testing_BoolValue()} - set {_compressed = newValue} - } - /// Returns true if `compressed` has been explicitly set. - var hasCompressed: Bool {return self._compressed != nil} - /// Clears the value of `compressed`. Subsequent reads from it will return its default value. - mutating func clearCompressed() {self._compressed = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _compressed: Grpc_Testing_BoolValue? = nil -} - -/// Server-streaming request. -struct Grpc_Testing_StreamingOutputCallRequest: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// Desired payload type in the response from the server. - /// If response_type is RANDOM, the payload from each response in the stream - /// might be of different types. This is to simulate a mixed type of payload - /// stream. - var responseType: Grpc_Testing_PayloadType = .compressable - - /// Configuration for each expected response message. - var responseParameters: [Grpc_Testing_ResponseParameters] = [] - - /// Optional input payload sent along with the request. - var payload: Grpc_Testing_Payload { - get {return _payload ?? Grpc_Testing_Payload()} - set {_payload = newValue} - } - /// Returns true if `payload` has been explicitly set. - var hasPayload: Bool {return self._payload != nil} - /// Clears the value of `payload`. Subsequent reads from it will return its default value. - mutating func clearPayload() {self._payload = nil} - - /// Whether server should return a given status - var responseStatus: Grpc_Testing_EchoStatus { - get {return _responseStatus ?? Grpc_Testing_EchoStatus()} - set {_responseStatus = newValue} - } - /// Returns true if `responseStatus` has been explicitly set. - var hasResponseStatus: Bool {return self._responseStatus != nil} - /// Clears the value of `responseStatus`. Subsequent reads from it will return its default value. - mutating func clearResponseStatus() {self._responseStatus = nil} - - /// If set the server should update this metrics report data at the OOB server. - var orcaOobReport: Grpc_Testing_TestOrcaReport { - get {return _orcaOobReport ?? Grpc_Testing_TestOrcaReport()} - set {_orcaOobReport = newValue} - } - /// Returns true if `orcaOobReport` has been explicitly set. - var hasOrcaOobReport: Bool {return self._orcaOobReport != nil} - /// Clears the value of `orcaOobReport`. Subsequent reads from it will return its default value. - mutating func clearOrcaOobReport() {self._orcaOobReport = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _payload: Grpc_Testing_Payload? = nil - fileprivate var _responseStatus: Grpc_Testing_EchoStatus? = nil - fileprivate var _orcaOobReport: Grpc_Testing_TestOrcaReport? = nil -} - -/// Server-streaming response, as configured by the request and parameters. -struct Grpc_Testing_StreamingOutputCallResponse: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// Payload to increase response size. - var payload: Grpc_Testing_Payload { - get {return _payload ?? Grpc_Testing_Payload()} - set {_payload = newValue} - } - /// Returns true if `payload` has been explicitly set. - var hasPayload: Bool {return self._payload != nil} - /// Clears the value of `payload`. Subsequent reads from it will return its default value. - mutating func clearPayload() {self._payload = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _payload: Grpc_Testing_Payload? = nil -} - -/// For reconnect interop test only. -/// Client tells server what reconnection parameters it used. -struct Grpc_Testing_ReconnectParams: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var maxReconnectBackoffMs: Int32 = 0 - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -/// For reconnect interop test only. -/// Server tells client whether its reconnects are following the spec and the -/// reconnect backoffs it saw. -struct Grpc_Testing_ReconnectInfo: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var passed: Bool = false - - var backoffMs: [Int32] = [] - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -struct Grpc_Testing_LoadBalancerStatsRequest: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// Request stats for the next num_rpcs sent by client. - var numRpcs: Int32 = 0 - - /// If num_rpcs have not completed within timeout_sec, return partial results. - var timeoutSec: Int32 = 0 - - /// Response header + trailer metadata entries we want the values of. - /// Matching of the keys is case-insensitive as per rfc7540#section-8.1.2 - /// * (asterisk) is a special value that will return all metadata entries - var metadataKeys: [String] = [] - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -struct Grpc_Testing_LoadBalancerStatsResponse: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// The number of completed RPCs for each peer. - var rpcsByPeer: Dictionary = [:] - - /// The number of RPCs that failed to record a remote peer. - var numFailures: Int32 = 0 - - var rpcsByMethod: Dictionary = [:] - - /// All the metadata of all RPCs for each peer. - var metadatasByPeer: Dictionary = [:] - - var unknownFields = SwiftProtobuf.UnknownStorage() - - enum MetadataType: SwiftProtobuf.Enum, Swift.CaseIterable { - typealias RawValue = Int - case unknown // = 0 - case initial // = 1 - case trailing // = 2 - case UNRECOGNIZED(Int) - - init() { - self = .unknown - } - - init?(rawValue: Int) { - switch rawValue { - case 0: self = .unknown - case 1: self = .initial - case 2: self = .trailing - default: self = .UNRECOGNIZED(rawValue) - } - } - - var rawValue: Int { - switch self { - case .unknown: return 0 - case .initial: return 1 - case .trailing: return 2 - case .UNRECOGNIZED(let i): return i - } - } - - // The compiler won't synthesize support with the UNRECOGNIZED case. - static let allCases: [Grpc_Testing_LoadBalancerStatsResponse.MetadataType] = [ - .unknown, - .initial, - .trailing, - ] - - } - - struct MetadataEntry: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// Key, exactly as received from the server. Case may be different from what - /// was requested in the LoadBalancerStatsRequest) - var key: String = String() - - /// Value, exactly as received from the server. - var value: String = String() - - /// Metadata type - var type: Grpc_Testing_LoadBalancerStatsResponse.MetadataType = .unknown - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - } - - struct RpcMetadata: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// metadata values for each rpc for the keys specified in - /// LoadBalancerStatsRequest.metadata_keys. - var metadata: [Grpc_Testing_LoadBalancerStatsResponse.MetadataEntry] = [] - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - } - - struct MetadataByPeer: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// List of RpcMetadata in for each RPC with a given peer - var rpcMetadata: [Grpc_Testing_LoadBalancerStatsResponse.RpcMetadata] = [] - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - } - - struct RpcsByPeer: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// The number of completed RPCs for each peer. - var rpcsByPeer: Dictionary = [:] - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - } - - init() {} -} - -/// Request for retrieving a test client's accumulated stats. -struct Grpc_Testing_LoadBalancerAccumulatedStatsRequest: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -/// Accumulated stats for RPCs sent by a test client. -struct Grpc_Testing_LoadBalancerAccumulatedStatsResponse: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// The total number of RPCs have ever issued for each type. - /// Deprecated: use stats_per_method.rpcs_started instead. - /// - /// NOTE: This field was marked as deprecated in the .proto file. - var numRpcsStartedByMethod: Dictionary = [:] - - /// The total number of RPCs have ever completed successfully for each type. - /// Deprecated: use stats_per_method.result instead. - /// - /// NOTE: This field was marked as deprecated in the .proto file. - var numRpcsSucceededByMethod: Dictionary = [:] - - /// The total number of RPCs have ever failed for each type. - /// Deprecated: use stats_per_method.result instead. - /// - /// NOTE: This field was marked as deprecated in the .proto file. - var numRpcsFailedByMethod: Dictionary = [:] - - /// Per-method RPC statistics. The key is the RpcType in string form; e.g. - /// 'EMPTY_CALL' or 'UNARY_CALL' - var statsPerMethod: Dictionary = [:] - - var unknownFields = SwiftProtobuf.UnknownStorage() - - struct MethodStats: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// The number of RPCs that were started for this method. - var rpcsStarted: Int32 = 0 - - /// The number of RPCs that completed with each status for this method. The - /// key is the integral value of a google.rpc.Code; the value is the count. - var result: Dictionary = [:] - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - } - - init() {} -} - -/// Configurations for a test client. -struct Grpc_Testing_ClientConfigureRequest: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// The types of RPCs the client sends. - var types: [Grpc_Testing_ClientConfigureRequest.RpcType] = [] - - /// The collection of custom metadata to be attached to RPCs sent by the client. - var metadata: [Grpc_Testing_ClientConfigureRequest.Metadata] = [] - - /// The deadline to use, in seconds, for all RPCs. If unset or zero, the - /// client will use the default from the command-line. - var timeoutSec: Int32 = 0 - - var unknownFields = SwiftProtobuf.UnknownStorage() - - /// Type of RPCs to send. - enum RpcType: SwiftProtobuf.Enum, Swift.CaseIterable { - typealias RawValue = Int - case emptyCall // = 0 - case unaryCall // = 1 - case UNRECOGNIZED(Int) - - init() { - self = .emptyCall - } - - init?(rawValue: Int) { - switch rawValue { - case 0: self = .emptyCall - case 1: self = .unaryCall - default: self = .UNRECOGNIZED(rawValue) - } - } - - var rawValue: Int { - switch self { - case .emptyCall: return 0 - case .unaryCall: return 1 - case .UNRECOGNIZED(let i): return i - } - } - - // The compiler won't synthesize support with the UNRECOGNIZED case. - static let allCases: [Grpc_Testing_ClientConfigureRequest.RpcType] = [ - .emptyCall, - .unaryCall, - ] - - } - - /// Metadata to be attached for the given type of RPCs. - struct Metadata: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var type: Grpc_Testing_ClientConfigureRequest.RpcType = .emptyCall - - var key: String = String() - - var value: String = String() - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - } - - init() {} -} - -/// Response for updating a test client's configuration. -struct Grpc_Testing_ClientConfigureResponse: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -struct Grpc_Testing_MemorySize: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var rss: Int64 = 0 - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -/// Metrics data the server will update and send to the client. It mirrors orca load report -/// https://github.com/cncf/xds/blob/eded343319d09f30032952beda9840bbd3dcf7ac/xds/data/orca/v3/orca_load_report.proto#L15, -/// but avoids orca dependency. Used by both per-query and out-of-band reporting tests. -struct Grpc_Testing_TestOrcaReport: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var cpuUtilization: Double = 0 - - var memoryUtilization: Double = 0 - - var requestCost: Dictionary = [:] - - var utilization: Dictionary = [:] - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -/// Status that will be return to callers of the Hook method -struct Grpc_Testing_SetReturnStatusRequest: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var grpcCodeToReturn: Int32 = 0 - - var grpcStatusDescription: String = String() - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -struct Grpc_Testing_HookRequest: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var command: Grpc_Testing_HookRequest.HookRequestCommand = .unspecified - - var grpcCodeToReturn: Int32 = 0 - - var grpcStatusDescription: String = String() - - /// Server port to listen to - var serverPort: Int32 = 0 - - var unknownFields = SwiftProtobuf.UnknownStorage() - - enum HookRequestCommand: SwiftProtobuf.Enum, Swift.CaseIterable { - typealias RawValue = Int - - /// Default value - case unspecified // = 0 - - /// Start the HTTP endpoint - case start // = 1 - - /// Stop - case stop // = 2 - - /// Return from HTTP GET/POST - case `return` // = 3 - case UNRECOGNIZED(Int) - - init() { - self = .unspecified - } - - init?(rawValue: Int) { - switch rawValue { - case 0: self = .unspecified - case 1: self = .start - case 2: self = .stop - case 3: self = .return - default: self = .UNRECOGNIZED(rawValue) - } - } - - var rawValue: Int { - switch self { - case .unspecified: return 0 - case .start: return 1 - case .stop: return 2 - case .return: return 3 - case .UNRECOGNIZED(let i): return i - } - } - - // The compiler won't synthesize support with the UNRECOGNIZED case. - static let allCases: [Grpc_Testing_HookRequest.HookRequestCommand] = [ - .unspecified, - .start, - .stop, - .return, - ] - - } - - init() {} -} - -struct Grpc_Testing_HookResponse: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -// MARK: - Code below here is support for the SwiftProtobuf runtime. - -fileprivate let _protobuf_package = "grpc.testing" - -extension Grpc_Testing_PayloadType: SwiftProtobuf._ProtoNameProviding { - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 0: .same(proto: "COMPRESSABLE"), - ] -} - -extension Grpc_Testing_GrpclbRouteType: SwiftProtobuf._ProtoNameProviding { - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 0: .same(proto: "GRPCLB_ROUTE_TYPE_UNKNOWN"), - 1: .same(proto: "GRPCLB_ROUTE_TYPE_FALLBACK"), - 2: .same(proto: "GRPCLB_ROUTE_TYPE_BACKEND"), - ] -} - -extension Grpc_Testing_BoolValue: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".BoolValue" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "value"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularBoolField(value: &self.value) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if self.value != false { - try visitor.visitSingularBoolField(value: self.value, fieldNumber: 1) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_BoolValue, rhs: Grpc_Testing_BoolValue) -> Bool { - if lhs.value != rhs.value {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_Payload: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".Payload" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "type"), - 2: .same(proto: "body"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularEnumField(value: &self.type) }() - case 2: try { try decoder.decodeSingularBytesField(value: &self.body) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if self.type != .compressable { - try visitor.visitSingularEnumField(value: self.type, fieldNumber: 1) - } - if !self.body.isEmpty { - try visitor.visitSingularBytesField(value: self.body, fieldNumber: 2) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_Payload, rhs: Grpc_Testing_Payload) -> Bool { - if lhs.type != rhs.type {return false} - if lhs.body != rhs.body {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_EchoStatus: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".EchoStatus" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "code"), - 2: .same(proto: "message"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularInt32Field(value: &self.code) }() - case 2: try { try decoder.decodeSingularStringField(value: &self.message) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if self.code != 0 { - try visitor.visitSingularInt32Field(value: self.code, fieldNumber: 1) - } - if !self.message.isEmpty { - try visitor.visitSingularStringField(value: self.message, fieldNumber: 2) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_EchoStatus, rhs: Grpc_Testing_EchoStatus) -> Bool { - if lhs.code != rhs.code {return false} - if lhs.message != rhs.message {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_SimpleRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".SimpleRequest" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "response_type"), - 2: .standard(proto: "response_size"), - 3: .same(proto: "payload"), - 4: .standard(proto: "fill_username"), - 5: .standard(proto: "fill_oauth_scope"), - 6: .standard(proto: "response_compressed"), - 7: .standard(proto: "response_status"), - 8: .standard(proto: "expect_compressed"), - 9: .standard(proto: "fill_server_id"), - 10: .standard(proto: "fill_grpclb_route_type"), - 11: .standard(proto: "orca_per_query_report"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularEnumField(value: &self.responseType) }() - case 2: try { try decoder.decodeSingularInt32Field(value: &self.responseSize) }() - case 3: try { try decoder.decodeSingularMessageField(value: &self._payload) }() - case 4: try { try decoder.decodeSingularBoolField(value: &self.fillUsername) }() - case 5: try { try decoder.decodeSingularBoolField(value: &self.fillOauthScope) }() - case 6: try { try decoder.decodeSingularMessageField(value: &self._responseCompressed) }() - case 7: try { try decoder.decodeSingularMessageField(value: &self._responseStatus) }() - case 8: try { try decoder.decodeSingularMessageField(value: &self._expectCompressed) }() - case 9: try { try decoder.decodeSingularBoolField(value: &self.fillServerID) }() - case 10: try { try decoder.decodeSingularBoolField(value: &self.fillGrpclbRouteType) }() - case 11: try { try decoder.decodeSingularMessageField(value: &self._orcaPerQueryReport) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 - if self.responseType != .compressable { - try visitor.visitSingularEnumField(value: self.responseType, fieldNumber: 1) - } - if self.responseSize != 0 { - try visitor.visitSingularInt32Field(value: self.responseSize, fieldNumber: 2) - } - try { if let v = self._payload { - try visitor.visitSingularMessageField(value: v, fieldNumber: 3) - } }() - if self.fillUsername != false { - try visitor.visitSingularBoolField(value: self.fillUsername, fieldNumber: 4) - } - if self.fillOauthScope != false { - try visitor.visitSingularBoolField(value: self.fillOauthScope, fieldNumber: 5) - } - try { if let v = self._responseCompressed { - try visitor.visitSingularMessageField(value: v, fieldNumber: 6) - } }() - try { if let v = self._responseStatus { - try visitor.visitSingularMessageField(value: v, fieldNumber: 7) - } }() - try { if let v = self._expectCompressed { - try visitor.visitSingularMessageField(value: v, fieldNumber: 8) - } }() - if self.fillServerID != false { - try visitor.visitSingularBoolField(value: self.fillServerID, fieldNumber: 9) - } - if self.fillGrpclbRouteType != false { - try visitor.visitSingularBoolField(value: self.fillGrpclbRouteType, fieldNumber: 10) - } - try { if let v = self._orcaPerQueryReport { - try visitor.visitSingularMessageField(value: v, fieldNumber: 11) - } }() - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_SimpleRequest, rhs: Grpc_Testing_SimpleRequest) -> Bool { - if lhs.responseType != rhs.responseType {return false} - if lhs.responseSize != rhs.responseSize {return false} - if lhs._payload != rhs._payload {return false} - if lhs.fillUsername != rhs.fillUsername {return false} - if lhs.fillOauthScope != rhs.fillOauthScope {return false} - if lhs._responseCompressed != rhs._responseCompressed {return false} - if lhs._responseStatus != rhs._responseStatus {return false} - if lhs._expectCompressed != rhs._expectCompressed {return false} - if lhs.fillServerID != rhs.fillServerID {return false} - if lhs.fillGrpclbRouteType != rhs.fillGrpclbRouteType {return false} - if lhs._orcaPerQueryReport != rhs._orcaPerQueryReport {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_SimpleResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".SimpleResponse" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "payload"), - 2: .same(proto: "username"), - 3: .standard(proto: "oauth_scope"), - 4: .standard(proto: "server_id"), - 5: .standard(proto: "grpclb_route_type"), - 6: .same(proto: "hostname"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularMessageField(value: &self._payload) }() - case 2: try { try decoder.decodeSingularStringField(value: &self.username) }() - case 3: try { try decoder.decodeSingularStringField(value: &self.oauthScope) }() - case 4: try { try decoder.decodeSingularStringField(value: &self.serverID) }() - case 5: try { try decoder.decodeSingularEnumField(value: &self.grpclbRouteType) }() - case 6: try { try decoder.decodeSingularStringField(value: &self.hostname) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 - try { if let v = self._payload { - try visitor.visitSingularMessageField(value: v, fieldNumber: 1) - } }() - if !self.username.isEmpty { - try visitor.visitSingularStringField(value: self.username, fieldNumber: 2) - } - if !self.oauthScope.isEmpty { - try visitor.visitSingularStringField(value: self.oauthScope, fieldNumber: 3) - } - if !self.serverID.isEmpty { - try visitor.visitSingularStringField(value: self.serverID, fieldNumber: 4) - } - if self.grpclbRouteType != .unknown { - try visitor.visitSingularEnumField(value: self.grpclbRouteType, fieldNumber: 5) - } - if !self.hostname.isEmpty { - try visitor.visitSingularStringField(value: self.hostname, fieldNumber: 6) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_SimpleResponse, rhs: Grpc_Testing_SimpleResponse) -> Bool { - if lhs._payload != rhs._payload {return false} - if lhs.username != rhs.username {return false} - if lhs.oauthScope != rhs.oauthScope {return false} - if lhs.serverID != rhs.serverID {return false} - if lhs.grpclbRouteType != rhs.grpclbRouteType {return false} - if lhs.hostname != rhs.hostname {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_StreamingInputCallRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".StreamingInputCallRequest" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "payload"), - 2: .standard(proto: "expect_compressed"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularMessageField(value: &self._payload) }() - case 2: try { try decoder.decodeSingularMessageField(value: &self._expectCompressed) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 - try { if let v = self._payload { - try visitor.visitSingularMessageField(value: v, fieldNumber: 1) - } }() - try { if let v = self._expectCompressed { - try visitor.visitSingularMessageField(value: v, fieldNumber: 2) - } }() - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_StreamingInputCallRequest, rhs: Grpc_Testing_StreamingInputCallRequest) -> Bool { - if lhs._payload != rhs._payload {return false} - if lhs._expectCompressed != rhs._expectCompressed {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_StreamingInputCallResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".StreamingInputCallResponse" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "aggregated_payload_size"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularInt32Field(value: &self.aggregatedPayloadSize) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if self.aggregatedPayloadSize != 0 { - try visitor.visitSingularInt32Field(value: self.aggregatedPayloadSize, fieldNumber: 1) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_StreamingInputCallResponse, rhs: Grpc_Testing_StreamingInputCallResponse) -> Bool { - if lhs.aggregatedPayloadSize != rhs.aggregatedPayloadSize {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_ResponseParameters: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".ResponseParameters" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "size"), - 2: .standard(proto: "interval_us"), - 3: .same(proto: "compressed"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularInt32Field(value: &self.size) }() - case 2: try { try decoder.decodeSingularInt32Field(value: &self.intervalUs) }() - case 3: try { try decoder.decodeSingularMessageField(value: &self._compressed) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 - if self.size != 0 { - try visitor.visitSingularInt32Field(value: self.size, fieldNumber: 1) - } - if self.intervalUs != 0 { - try visitor.visitSingularInt32Field(value: self.intervalUs, fieldNumber: 2) - } - try { if let v = self._compressed { - try visitor.visitSingularMessageField(value: v, fieldNumber: 3) - } }() - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_ResponseParameters, rhs: Grpc_Testing_ResponseParameters) -> Bool { - if lhs.size != rhs.size {return false} - if lhs.intervalUs != rhs.intervalUs {return false} - if lhs._compressed != rhs._compressed {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_StreamingOutputCallRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".StreamingOutputCallRequest" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "response_type"), - 2: .standard(proto: "response_parameters"), - 3: .same(proto: "payload"), - 7: .standard(proto: "response_status"), - 8: .standard(proto: "orca_oob_report"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularEnumField(value: &self.responseType) }() - case 2: try { try decoder.decodeRepeatedMessageField(value: &self.responseParameters) }() - case 3: try { try decoder.decodeSingularMessageField(value: &self._payload) }() - case 7: try { try decoder.decodeSingularMessageField(value: &self._responseStatus) }() - case 8: try { try decoder.decodeSingularMessageField(value: &self._orcaOobReport) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 - if self.responseType != .compressable { - try visitor.visitSingularEnumField(value: self.responseType, fieldNumber: 1) - } - if !self.responseParameters.isEmpty { - try visitor.visitRepeatedMessageField(value: self.responseParameters, fieldNumber: 2) - } - try { if let v = self._payload { - try visitor.visitSingularMessageField(value: v, fieldNumber: 3) - } }() - try { if let v = self._responseStatus { - try visitor.visitSingularMessageField(value: v, fieldNumber: 7) - } }() - try { if let v = self._orcaOobReport { - try visitor.visitSingularMessageField(value: v, fieldNumber: 8) - } }() - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_StreamingOutputCallRequest, rhs: Grpc_Testing_StreamingOutputCallRequest) -> Bool { - if lhs.responseType != rhs.responseType {return false} - if lhs.responseParameters != rhs.responseParameters {return false} - if lhs._payload != rhs._payload {return false} - if lhs._responseStatus != rhs._responseStatus {return false} - if lhs._orcaOobReport != rhs._orcaOobReport {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_StreamingOutputCallResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".StreamingOutputCallResponse" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "payload"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularMessageField(value: &self._payload) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 - try { if let v = self._payload { - try visitor.visitSingularMessageField(value: v, fieldNumber: 1) - } }() - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_StreamingOutputCallResponse, rhs: Grpc_Testing_StreamingOutputCallResponse) -> Bool { - if lhs._payload != rhs._payload {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_ReconnectParams: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".ReconnectParams" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "max_reconnect_backoff_ms"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularInt32Field(value: &self.maxReconnectBackoffMs) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if self.maxReconnectBackoffMs != 0 { - try visitor.visitSingularInt32Field(value: self.maxReconnectBackoffMs, fieldNumber: 1) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_ReconnectParams, rhs: Grpc_Testing_ReconnectParams) -> Bool { - if lhs.maxReconnectBackoffMs != rhs.maxReconnectBackoffMs {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_ReconnectInfo: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".ReconnectInfo" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "passed"), - 2: .standard(proto: "backoff_ms"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularBoolField(value: &self.passed) }() - case 2: try { try decoder.decodeRepeatedInt32Field(value: &self.backoffMs) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if self.passed != false { - try visitor.visitSingularBoolField(value: self.passed, fieldNumber: 1) - } - if !self.backoffMs.isEmpty { - try visitor.visitPackedInt32Field(value: self.backoffMs, fieldNumber: 2) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_ReconnectInfo, rhs: Grpc_Testing_ReconnectInfo) -> Bool { - if lhs.passed != rhs.passed {return false} - if lhs.backoffMs != rhs.backoffMs {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_LoadBalancerStatsRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".LoadBalancerStatsRequest" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "num_rpcs"), - 2: .standard(proto: "timeout_sec"), - 3: .standard(proto: "metadata_keys"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularInt32Field(value: &self.numRpcs) }() - case 2: try { try decoder.decodeSingularInt32Field(value: &self.timeoutSec) }() - case 3: try { try decoder.decodeRepeatedStringField(value: &self.metadataKeys) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if self.numRpcs != 0 { - try visitor.visitSingularInt32Field(value: self.numRpcs, fieldNumber: 1) - } - if self.timeoutSec != 0 { - try visitor.visitSingularInt32Field(value: self.timeoutSec, fieldNumber: 2) - } - if !self.metadataKeys.isEmpty { - try visitor.visitRepeatedStringField(value: self.metadataKeys, fieldNumber: 3) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_LoadBalancerStatsRequest, rhs: Grpc_Testing_LoadBalancerStatsRequest) -> Bool { - if lhs.numRpcs != rhs.numRpcs {return false} - if lhs.timeoutSec != rhs.timeoutSec {return false} - if lhs.metadataKeys != rhs.metadataKeys {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_LoadBalancerStatsResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".LoadBalancerStatsResponse" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "rpcs_by_peer"), - 2: .standard(proto: "num_failures"), - 3: .standard(proto: "rpcs_by_method"), - 4: .standard(proto: "metadatas_by_peer"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeMapField(fieldType: SwiftProtobuf._ProtobufMap.self, value: &self.rpcsByPeer) }() - case 2: try { try decoder.decodeSingularInt32Field(value: &self.numFailures) }() - case 3: try { try decoder.decodeMapField(fieldType: SwiftProtobuf._ProtobufMessageMap.self, value: &self.rpcsByMethod) }() - case 4: try { try decoder.decodeMapField(fieldType: SwiftProtobuf._ProtobufMessageMap.self, value: &self.metadatasByPeer) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if !self.rpcsByPeer.isEmpty { - try visitor.visitMapField(fieldType: SwiftProtobuf._ProtobufMap.self, value: self.rpcsByPeer, fieldNumber: 1) - } - if self.numFailures != 0 { - try visitor.visitSingularInt32Field(value: self.numFailures, fieldNumber: 2) - } - if !self.rpcsByMethod.isEmpty { - try visitor.visitMapField(fieldType: SwiftProtobuf._ProtobufMessageMap.self, value: self.rpcsByMethod, fieldNumber: 3) - } - if !self.metadatasByPeer.isEmpty { - try visitor.visitMapField(fieldType: SwiftProtobuf._ProtobufMessageMap.self, value: self.metadatasByPeer, fieldNumber: 4) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_LoadBalancerStatsResponse, rhs: Grpc_Testing_LoadBalancerStatsResponse) -> Bool { - if lhs.rpcsByPeer != rhs.rpcsByPeer {return false} - if lhs.numFailures != rhs.numFailures {return false} - if lhs.rpcsByMethod != rhs.rpcsByMethod {return false} - if lhs.metadatasByPeer != rhs.metadatasByPeer {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_LoadBalancerStatsResponse.MetadataType: SwiftProtobuf._ProtoNameProviding { - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 0: .same(proto: "UNKNOWN"), - 1: .same(proto: "INITIAL"), - 2: .same(proto: "TRAILING"), - ] -} - -extension Grpc_Testing_LoadBalancerStatsResponse.MetadataEntry: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = Grpc_Testing_LoadBalancerStatsResponse.protoMessageName + ".MetadataEntry" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "key"), - 2: .same(proto: "value"), - 3: .same(proto: "type"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularStringField(value: &self.key) }() - case 2: try { try decoder.decodeSingularStringField(value: &self.value) }() - case 3: try { try decoder.decodeSingularEnumField(value: &self.type) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if !self.key.isEmpty { - try visitor.visitSingularStringField(value: self.key, fieldNumber: 1) - } - if !self.value.isEmpty { - try visitor.visitSingularStringField(value: self.value, fieldNumber: 2) - } - if self.type != .unknown { - try visitor.visitSingularEnumField(value: self.type, fieldNumber: 3) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_LoadBalancerStatsResponse.MetadataEntry, rhs: Grpc_Testing_LoadBalancerStatsResponse.MetadataEntry) -> Bool { - if lhs.key != rhs.key {return false} - if lhs.value != rhs.value {return false} - if lhs.type != rhs.type {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_LoadBalancerStatsResponse.RpcMetadata: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = Grpc_Testing_LoadBalancerStatsResponse.protoMessageName + ".RpcMetadata" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "metadata"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeRepeatedMessageField(value: &self.metadata) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if !self.metadata.isEmpty { - try visitor.visitRepeatedMessageField(value: self.metadata, fieldNumber: 1) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_LoadBalancerStatsResponse.RpcMetadata, rhs: Grpc_Testing_LoadBalancerStatsResponse.RpcMetadata) -> Bool { - if lhs.metadata != rhs.metadata {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_LoadBalancerStatsResponse.MetadataByPeer: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = Grpc_Testing_LoadBalancerStatsResponse.protoMessageName + ".MetadataByPeer" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "rpc_metadata"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeRepeatedMessageField(value: &self.rpcMetadata) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if !self.rpcMetadata.isEmpty { - try visitor.visitRepeatedMessageField(value: self.rpcMetadata, fieldNumber: 1) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_LoadBalancerStatsResponse.MetadataByPeer, rhs: Grpc_Testing_LoadBalancerStatsResponse.MetadataByPeer) -> Bool { - if lhs.rpcMetadata != rhs.rpcMetadata {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_LoadBalancerStatsResponse.RpcsByPeer: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = Grpc_Testing_LoadBalancerStatsResponse.protoMessageName + ".RpcsByPeer" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "rpcs_by_peer"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeMapField(fieldType: SwiftProtobuf._ProtobufMap.self, value: &self.rpcsByPeer) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if !self.rpcsByPeer.isEmpty { - try visitor.visitMapField(fieldType: SwiftProtobuf._ProtobufMap.self, value: self.rpcsByPeer, fieldNumber: 1) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_LoadBalancerStatsResponse.RpcsByPeer, rhs: Grpc_Testing_LoadBalancerStatsResponse.RpcsByPeer) -> Bool { - if lhs.rpcsByPeer != rhs.rpcsByPeer {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_LoadBalancerAccumulatedStatsRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".LoadBalancerAccumulatedStatsRequest" - static let _protobuf_nameMap = SwiftProtobuf._NameMap() - - mutating func decodeMessage(decoder: inout D) throws { - // Load everything into unknown fields - while try decoder.nextFieldNumber() != nil {} - } - - func traverse(visitor: inout V) throws { - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_LoadBalancerAccumulatedStatsRequest, rhs: Grpc_Testing_LoadBalancerAccumulatedStatsRequest) -> Bool { - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_LoadBalancerAccumulatedStatsResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".LoadBalancerAccumulatedStatsResponse" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "num_rpcs_started_by_method"), - 2: .standard(proto: "num_rpcs_succeeded_by_method"), - 3: .standard(proto: "num_rpcs_failed_by_method"), - 4: .standard(proto: "stats_per_method"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeMapField(fieldType: SwiftProtobuf._ProtobufMap.self, value: &self.numRpcsStartedByMethod) }() - case 2: try { try decoder.decodeMapField(fieldType: SwiftProtobuf._ProtobufMap.self, value: &self.numRpcsSucceededByMethod) }() - case 3: try { try decoder.decodeMapField(fieldType: SwiftProtobuf._ProtobufMap.self, value: &self.numRpcsFailedByMethod) }() - case 4: try { try decoder.decodeMapField(fieldType: SwiftProtobuf._ProtobufMessageMap.self, value: &self.statsPerMethod) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if !self.numRpcsStartedByMethod.isEmpty { - try visitor.visitMapField(fieldType: SwiftProtobuf._ProtobufMap.self, value: self.numRpcsStartedByMethod, fieldNumber: 1) - } - if !self.numRpcsSucceededByMethod.isEmpty { - try visitor.visitMapField(fieldType: SwiftProtobuf._ProtobufMap.self, value: self.numRpcsSucceededByMethod, fieldNumber: 2) - } - if !self.numRpcsFailedByMethod.isEmpty { - try visitor.visitMapField(fieldType: SwiftProtobuf._ProtobufMap.self, value: self.numRpcsFailedByMethod, fieldNumber: 3) - } - if !self.statsPerMethod.isEmpty { - try visitor.visitMapField(fieldType: SwiftProtobuf._ProtobufMessageMap.self, value: self.statsPerMethod, fieldNumber: 4) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_LoadBalancerAccumulatedStatsResponse, rhs: Grpc_Testing_LoadBalancerAccumulatedStatsResponse) -> Bool { - if lhs.numRpcsStartedByMethod != rhs.numRpcsStartedByMethod {return false} - if lhs.numRpcsSucceededByMethod != rhs.numRpcsSucceededByMethod {return false} - if lhs.numRpcsFailedByMethod != rhs.numRpcsFailedByMethod {return false} - if lhs.statsPerMethod != rhs.statsPerMethod {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_LoadBalancerAccumulatedStatsResponse.MethodStats: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = Grpc_Testing_LoadBalancerAccumulatedStatsResponse.protoMessageName + ".MethodStats" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "rpcs_started"), - 2: .same(proto: "result"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularInt32Field(value: &self.rpcsStarted) }() - case 2: try { try decoder.decodeMapField(fieldType: SwiftProtobuf._ProtobufMap.self, value: &self.result) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if self.rpcsStarted != 0 { - try visitor.visitSingularInt32Field(value: self.rpcsStarted, fieldNumber: 1) - } - if !self.result.isEmpty { - try visitor.visitMapField(fieldType: SwiftProtobuf._ProtobufMap.self, value: self.result, fieldNumber: 2) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_LoadBalancerAccumulatedStatsResponse.MethodStats, rhs: Grpc_Testing_LoadBalancerAccumulatedStatsResponse.MethodStats) -> Bool { - if lhs.rpcsStarted != rhs.rpcsStarted {return false} - if lhs.result != rhs.result {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_ClientConfigureRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".ClientConfigureRequest" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "types"), - 2: .same(proto: "metadata"), - 3: .standard(proto: "timeout_sec"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeRepeatedEnumField(value: &self.types) }() - case 2: try { try decoder.decodeRepeatedMessageField(value: &self.metadata) }() - case 3: try { try decoder.decodeSingularInt32Field(value: &self.timeoutSec) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if !self.types.isEmpty { - try visitor.visitPackedEnumField(value: self.types, fieldNumber: 1) - } - if !self.metadata.isEmpty { - try visitor.visitRepeatedMessageField(value: self.metadata, fieldNumber: 2) - } - if self.timeoutSec != 0 { - try visitor.visitSingularInt32Field(value: self.timeoutSec, fieldNumber: 3) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_ClientConfigureRequest, rhs: Grpc_Testing_ClientConfigureRequest) -> Bool { - if lhs.types != rhs.types {return false} - if lhs.metadata != rhs.metadata {return false} - if lhs.timeoutSec != rhs.timeoutSec {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_ClientConfigureRequest.RpcType: SwiftProtobuf._ProtoNameProviding { - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 0: .same(proto: "EMPTY_CALL"), - 1: .same(proto: "UNARY_CALL"), - ] -} - -extension Grpc_Testing_ClientConfigureRequest.Metadata: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = Grpc_Testing_ClientConfigureRequest.protoMessageName + ".Metadata" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "type"), - 2: .same(proto: "key"), - 3: .same(proto: "value"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularEnumField(value: &self.type) }() - case 2: try { try decoder.decodeSingularStringField(value: &self.key) }() - case 3: try { try decoder.decodeSingularStringField(value: &self.value) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if self.type != .emptyCall { - try visitor.visitSingularEnumField(value: self.type, fieldNumber: 1) - } - if !self.key.isEmpty { - try visitor.visitSingularStringField(value: self.key, fieldNumber: 2) - } - if !self.value.isEmpty { - try visitor.visitSingularStringField(value: self.value, fieldNumber: 3) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_ClientConfigureRequest.Metadata, rhs: Grpc_Testing_ClientConfigureRequest.Metadata) -> Bool { - if lhs.type != rhs.type {return false} - if lhs.key != rhs.key {return false} - if lhs.value != rhs.value {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_ClientConfigureResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".ClientConfigureResponse" - static let _protobuf_nameMap = SwiftProtobuf._NameMap() - - mutating func decodeMessage(decoder: inout D) throws { - // Load everything into unknown fields - while try decoder.nextFieldNumber() != nil {} - } - - func traverse(visitor: inout V) throws { - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_ClientConfigureResponse, rhs: Grpc_Testing_ClientConfigureResponse) -> Bool { - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_MemorySize: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".MemorySize" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "rss"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularInt64Field(value: &self.rss) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if self.rss != 0 { - try visitor.visitSingularInt64Field(value: self.rss, fieldNumber: 1) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_MemorySize, rhs: Grpc_Testing_MemorySize) -> Bool { - if lhs.rss != rhs.rss {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_TestOrcaReport: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".TestOrcaReport" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "cpu_utilization"), - 2: .standard(proto: "memory_utilization"), - 3: .standard(proto: "request_cost"), - 4: .same(proto: "utilization"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularDoubleField(value: &self.cpuUtilization) }() - case 2: try { try decoder.decodeSingularDoubleField(value: &self.memoryUtilization) }() - case 3: try { try decoder.decodeMapField(fieldType: SwiftProtobuf._ProtobufMap.self, value: &self.requestCost) }() - case 4: try { try decoder.decodeMapField(fieldType: SwiftProtobuf._ProtobufMap.self, value: &self.utilization) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if self.cpuUtilization.bitPattern != 0 { - try visitor.visitSingularDoubleField(value: self.cpuUtilization, fieldNumber: 1) - } - if self.memoryUtilization.bitPattern != 0 { - try visitor.visitSingularDoubleField(value: self.memoryUtilization, fieldNumber: 2) - } - if !self.requestCost.isEmpty { - try visitor.visitMapField(fieldType: SwiftProtobuf._ProtobufMap.self, value: self.requestCost, fieldNumber: 3) - } - if !self.utilization.isEmpty { - try visitor.visitMapField(fieldType: SwiftProtobuf._ProtobufMap.self, value: self.utilization, fieldNumber: 4) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_TestOrcaReport, rhs: Grpc_Testing_TestOrcaReport) -> Bool { - if lhs.cpuUtilization != rhs.cpuUtilization {return false} - if lhs.memoryUtilization != rhs.memoryUtilization {return false} - if lhs.requestCost != rhs.requestCost {return false} - if lhs.utilization != rhs.utilization {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_SetReturnStatusRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".SetReturnStatusRequest" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "grpc_code_to_return"), - 2: .standard(proto: "grpc_status_description"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularInt32Field(value: &self.grpcCodeToReturn) }() - case 2: try { try decoder.decodeSingularStringField(value: &self.grpcStatusDescription) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if self.grpcCodeToReturn != 0 { - try visitor.visitSingularInt32Field(value: self.grpcCodeToReturn, fieldNumber: 1) - } - if !self.grpcStatusDescription.isEmpty { - try visitor.visitSingularStringField(value: self.grpcStatusDescription, fieldNumber: 2) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_SetReturnStatusRequest, rhs: Grpc_Testing_SetReturnStatusRequest) -> Bool { - if lhs.grpcCodeToReturn != rhs.grpcCodeToReturn {return false} - if lhs.grpcStatusDescription != rhs.grpcStatusDescription {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_HookRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".HookRequest" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "command"), - 2: .standard(proto: "grpc_code_to_return"), - 3: .standard(proto: "grpc_status_description"), - 4: .standard(proto: "server_port"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularEnumField(value: &self.command) }() - case 2: try { try decoder.decodeSingularInt32Field(value: &self.grpcCodeToReturn) }() - case 3: try { try decoder.decodeSingularStringField(value: &self.grpcStatusDescription) }() - case 4: try { try decoder.decodeSingularInt32Field(value: &self.serverPort) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if self.command != .unspecified { - try visitor.visitSingularEnumField(value: self.command, fieldNumber: 1) - } - if self.grpcCodeToReturn != 0 { - try visitor.visitSingularInt32Field(value: self.grpcCodeToReturn, fieldNumber: 2) - } - if !self.grpcStatusDescription.isEmpty { - try visitor.visitSingularStringField(value: self.grpcStatusDescription, fieldNumber: 3) - } - if self.serverPort != 0 { - try visitor.visitSingularInt32Field(value: self.serverPort, fieldNumber: 4) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_HookRequest, rhs: Grpc_Testing_HookRequest) -> Bool { - if lhs.command != rhs.command {return false} - if lhs.grpcCodeToReturn != rhs.grpcCodeToReturn {return false} - if lhs.grpcStatusDescription != rhs.grpcStatusDescription {return false} - if lhs.serverPort != rhs.serverPort {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_HookRequest.HookRequestCommand: SwiftProtobuf._ProtoNameProviding { - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 0: .same(proto: "UNSPECIFIED"), - 1: .same(proto: "START"), - 2: .same(proto: "STOP"), - 3: .same(proto: "RETURN"), - ] -} - -extension Grpc_Testing_HookResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".HookResponse" - static let _protobuf_nameMap = SwiftProtobuf._NameMap() - - mutating func decodeMessage(decoder: inout D) throws { - // Load everything into unknown fields - while try decoder.nextFieldNumber() != nil {} - } - - func traverse(visitor: inout V) throws { - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_HookResponse, rhs: Grpc_Testing_HookResponse) -> Bool { - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} diff --git a/Sources/performance-worker/Generated/grpc_testing_payloads.pb.swift b/Sources/performance-worker/Generated/grpc_testing_payloads.pb.swift deleted file mode 100644 index 8624160c0..000000000 --- a/Sources/performance-worker/Generated/grpc_testing_payloads.pb.swift +++ /dev/null @@ -1,305 +0,0 @@ -// DO NOT EDIT. -// swift-format-ignore-file -// swiftlint:disable all -// -// Generated by the Swift generator plugin for the protocol buffer compiler. -// Source: grpc/testing/payloads.proto -// -// For information on using the generated types, please see the documentation: -// https://github.com/apple/swift-protobuf/ - -// Copyright 2015 gRPC authors. -// -// 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 - -// If the compiler emits an error on this type, it is because this file -// was generated by a version of the `protoc` Swift plug-in that is -// incompatible with the version of SwiftProtobuf to which you are linking. -// Please ensure that you are building against the same version of the API -// that was used to generate this file. -fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { - struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} - typealias Version = _2 -} - -struct Grpc_Testing_ByteBufferParams: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var reqSize: Int32 = 0 - - var respSize: Int32 = 0 - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -struct Grpc_Testing_SimpleProtoParams: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var reqSize: Int32 = 0 - - var respSize: Int32 = 0 - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -/// TODO (vpai): Fill this in once the details of complex, representative -/// protos are decided -struct Grpc_Testing_ComplexProtoParams: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -struct Grpc_Testing_PayloadConfig: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var payload: Grpc_Testing_PayloadConfig.OneOf_Payload? = nil - - var bytebufParams: Grpc_Testing_ByteBufferParams { - get { - if case .bytebufParams(let v)? = payload {return v} - return Grpc_Testing_ByteBufferParams() - } - set {payload = .bytebufParams(newValue)} - } - - var simpleParams: Grpc_Testing_SimpleProtoParams { - get { - if case .simpleParams(let v)? = payload {return v} - return Grpc_Testing_SimpleProtoParams() - } - set {payload = .simpleParams(newValue)} - } - - var complexParams: Grpc_Testing_ComplexProtoParams { - get { - if case .complexParams(let v)? = payload {return v} - return Grpc_Testing_ComplexProtoParams() - } - set {payload = .complexParams(newValue)} - } - - var unknownFields = SwiftProtobuf.UnknownStorage() - - enum OneOf_Payload: Equatable, Sendable { - case bytebufParams(Grpc_Testing_ByteBufferParams) - case simpleParams(Grpc_Testing_SimpleProtoParams) - case complexParams(Grpc_Testing_ComplexProtoParams) - - } - - init() {} -} - -// MARK: - Code below here is support for the SwiftProtobuf runtime. - -fileprivate let _protobuf_package = "grpc.testing" - -extension Grpc_Testing_ByteBufferParams: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".ByteBufferParams" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "req_size"), - 2: .standard(proto: "resp_size"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularInt32Field(value: &self.reqSize) }() - case 2: try { try decoder.decodeSingularInt32Field(value: &self.respSize) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if self.reqSize != 0 { - try visitor.visitSingularInt32Field(value: self.reqSize, fieldNumber: 1) - } - if self.respSize != 0 { - try visitor.visitSingularInt32Field(value: self.respSize, fieldNumber: 2) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_ByteBufferParams, rhs: Grpc_Testing_ByteBufferParams) -> Bool { - if lhs.reqSize != rhs.reqSize {return false} - if lhs.respSize != rhs.respSize {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_SimpleProtoParams: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".SimpleProtoParams" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "req_size"), - 2: .standard(proto: "resp_size"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularInt32Field(value: &self.reqSize) }() - case 2: try { try decoder.decodeSingularInt32Field(value: &self.respSize) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if self.reqSize != 0 { - try visitor.visitSingularInt32Field(value: self.reqSize, fieldNumber: 1) - } - if self.respSize != 0 { - try visitor.visitSingularInt32Field(value: self.respSize, fieldNumber: 2) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_SimpleProtoParams, rhs: Grpc_Testing_SimpleProtoParams) -> Bool { - if lhs.reqSize != rhs.reqSize {return false} - if lhs.respSize != rhs.respSize {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_ComplexProtoParams: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".ComplexProtoParams" - static let _protobuf_nameMap = SwiftProtobuf._NameMap() - - mutating func decodeMessage(decoder: inout D) throws { - // Load everything into unknown fields - while try decoder.nextFieldNumber() != nil {} - } - - func traverse(visitor: inout V) throws { - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_ComplexProtoParams, rhs: Grpc_Testing_ComplexProtoParams) -> Bool { - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_PayloadConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".PayloadConfig" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "bytebuf_params"), - 2: .standard(proto: "simple_params"), - 3: .standard(proto: "complex_params"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { - var v: Grpc_Testing_ByteBufferParams? - var hadOneofValue = false - if let current = self.payload { - hadOneofValue = true - if case .bytebufParams(let m) = current {v = m} - } - try decoder.decodeSingularMessageField(value: &v) - if let v = v { - if hadOneofValue {try decoder.handleConflictingOneOf()} - self.payload = .bytebufParams(v) - } - }() - case 2: try { - var v: Grpc_Testing_SimpleProtoParams? - var hadOneofValue = false - if let current = self.payload { - hadOneofValue = true - if case .simpleParams(let m) = current {v = m} - } - try decoder.decodeSingularMessageField(value: &v) - if let v = v { - if hadOneofValue {try decoder.handleConflictingOneOf()} - self.payload = .simpleParams(v) - } - }() - case 3: try { - var v: Grpc_Testing_ComplexProtoParams? - var hadOneofValue = false - if let current = self.payload { - hadOneofValue = true - if case .complexParams(let m) = current {v = m} - } - try decoder.decodeSingularMessageField(value: &v) - if let v = v { - if hadOneofValue {try decoder.handleConflictingOneOf()} - self.payload = .complexParams(v) - } - }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 - switch self.payload { - case .bytebufParams?: try { - guard case .bytebufParams(let v)? = self.payload else { preconditionFailure() } - try visitor.visitSingularMessageField(value: v, fieldNumber: 1) - }() - case .simpleParams?: try { - guard case .simpleParams(let v)? = self.payload else { preconditionFailure() } - try visitor.visitSingularMessageField(value: v, fieldNumber: 2) - }() - case .complexParams?: try { - guard case .complexParams(let v)? = self.payload else { preconditionFailure() } - try visitor.visitSingularMessageField(value: v, fieldNumber: 3) - }() - case nil: break - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_PayloadConfig, rhs: Grpc_Testing_PayloadConfig) -> Bool { - if lhs.payload != rhs.payload {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} diff --git a/Sources/performance-worker/Generated/grpc_testing_stats.pb.swift b/Sources/performance-worker/Generated/grpc_testing_stats.pb.swift deleted file mode 100644 index 2b45d0bd0..000000000 --- a/Sources/performance-worker/Generated/grpc_testing_stats.pb.swift +++ /dev/null @@ -1,462 +0,0 @@ -// DO NOT EDIT. -// swift-format-ignore-file -// swiftlint:disable all -// -// Generated by the Swift generator plugin for the protocol buffer compiler. -// Source: grpc/testing/stats.proto -// -// For information on using the generated types, please see the documentation: -// https://github.com/apple/swift-protobuf/ - -// Copyright 2015 gRPC authors. -// -// 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 - -// If the compiler emits an error on this type, it is because this file -// was generated by a version of the `protoc` Swift plug-in that is -// incompatible with the version of SwiftProtobuf to which you are linking. -// Please ensure that you are building against the same version of the API -// that was used to generate this file. -fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { - struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} - typealias Version = _2 -} - -struct Grpc_Testing_ServerStats: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// wall clock time change in seconds since last reset - var timeElapsed: Double = 0 - - /// change in user time (in seconds) used by the server since last reset - var timeUser: Double = 0 - - /// change in server time (in seconds) used by the server process and all - /// threads since last reset - var timeSystem: Double = 0 - - /// change in total cpu time of the server (data from proc/stat) - var totalCpuTime: UInt64 = 0 - - /// change in idle time of the server (data from proc/stat) - var idleCpuTime: UInt64 = 0 - - /// Number of polls called inside completion queue - var cqPollCount: UInt64 = 0 - - /// Core library stats - var coreStats: Grpc_Core_Stats { - get {return _coreStats ?? Grpc_Core_Stats()} - set {_coreStats = newValue} - } - /// Returns true if `coreStats` has been explicitly set. - var hasCoreStats: Bool {return self._coreStats != nil} - /// Clears the value of `coreStats`. Subsequent reads from it will return its default value. - mutating func clearCoreStats() {self._coreStats = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _coreStats: Grpc_Core_Stats? = nil -} - -/// Histogram params based on grpc/support/histogram.c -struct Grpc_Testing_HistogramParams: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// first bucket is [0, 1 + resolution) - var resolution: Double = 0 - - /// use enough buckets to allow this value - var maxPossible: Double = 0 - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -/// Histogram data based on grpc/support/histogram.c -struct Grpc_Testing_HistogramData: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var bucket: [UInt32] = [] - - var minSeen: Double = 0 - - var maxSeen: Double = 0 - - var sum: Double = 0 - - var sumOfSquares: Double = 0 - - var count: Double = 0 - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -struct Grpc_Testing_RequestResultCount: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - var statusCode: Int32 = 0 - - var count: Int64 = 0 - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -struct Grpc_Testing_ClientStats: Sendable { - // SwiftProtobuf.Message conformance is added in an extension below. See the - // `Message` and `Message+*Additions` files in the SwiftProtobuf library for - // methods supported on all messages. - - /// Latency histogram. Data points are in nanoseconds. - var latencies: Grpc_Testing_HistogramData { - get {return _latencies ?? Grpc_Testing_HistogramData()} - set {_latencies = newValue} - } - /// Returns true if `latencies` has been explicitly set. - var hasLatencies: Bool {return self._latencies != nil} - /// Clears the value of `latencies`. Subsequent reads from it will return its default value. - mutating func clearLatencies() {self._latencies = nil} - - /// See ServerStats for details. - var timeElapsed: Double = 0 - - var timeUser: Double = 0 - - var timeSystem: Double = 0 - - /// Number of failed requests (one row per status code seen) - var requestResults: [Grpc_Testing_RequestResultCount] = [] - - /// Number of polls called inside completion queue - var cqPollCount: UInt64 = 0 - - /// Core library stats - var coreStats: Grpc_Core_Stats { - get {return _coreStats ?? Grpc_Core_Stats()} - set {_coreStats = newValue} - } - /// Returns true if `coreStats` has been explicitly set. - var hasCoreStats: Bool {return self._coreStats != nil} - /// Clears the value of `coreStats`. Subsequent reads from it will return its default value. - mutating func clearCoreStats() {self._coreStats = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _latencies: Grpc_Testing_HistogramData? = nil - fileprivate var _coreStats: Grpc_Core_Stats? = nil -} - -// MARK: - Code below here is support for the SwiftProtobuf runtime. - -fileprivate let _protobuf_package = "grpc.testing" - -extension Grpc_Testing_ServerStats: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".ServerStats" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "time_elapsed"), - 2: .standard(proto: "time_user"), - 3: .standard(proto: "time_system"), - 4: .standard(proto: "total_cpu_time"), - 5: .standard(proto: "idle_cpu_time"), - 6: .standard(proto: "cq_poll_count"), - 7: .standard(proto: "core_stats"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularDoubleField(value: &self.timeElapsed) }() - case 2: try { try decoder.decodeSingularDoubleField(value: &self.timeUser) }() - case 3: try { try decoder.decodeSingularDoubleField(value: &self.timeSystem) }() - case 4: try { try decoder.decodeSingularUInt64Field(value: &self.totalCpuTime) }() - case 5: try { try decoder.decodeSingularUInt64Field(value: &self.idleCpuTime) }() - case 6: try { try decoder.decodeSingularUInt64Field(value: &self.cqPollCount) }() - case 7: try { try decoder.decodeSingularMessageField(value: &self._coreStats) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 - if self.timeElapsed.bitPattern != 0 { - try visitor.visitSingularDoubleField(value: self.timeElapsed, fieldNumber: 1) - } - if self.timeUser.bitPattern != 0 { - try visitor.visitSingularDoubleField(value: self.timeUser, fieldNumber: 2) - } - if self.timeSystem.bitPattern != 0 { - try visitor.visitSingularDoubleField(value: self.timeSystem, fieldNumber: 3) - } - if self.totalCpuTime != 0 { - try visitor.visitSingularUInt64Field(value: self.totalCpuTime, fieldNumber: 4) - } - if self.idleCpuTime != 0 { - try visitor.visitSingularUInt64Field(value: self.idleCpuTime, fieldNumber: 5) - } - if self.cqPollCount != 0 { - try visitor.visitSingularUInt64Field(value: self.cqPollCount, fieldNumber: 6) - } - try { if let v = self._coreStats { - try visitor.visitSingularMessageField(value: v, fieldNumber: 7) - } }() - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_ServerStats, rhs: Grpc_Testing_ServerStats) -> Bool { - if lhs.timeElapsed != rhs.timeElapsed {return false} - if lhs.timeUser != rhs.timeUser {return false} - if lhs.timeSystem != rhs.timeSystem {return false} - if lhs.totalCpuTime != rhs.totalCpuTime {return false} - if lhs.idleCpuTime != rhs.idleCpuTime {return false} - if lhs.cqPollCount != rhs.cqPollCount {return false} - if lhs._coreStats != rhs._coreStats {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_HistogramParams: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".HistogramParams" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "resolution"), - 2: .standard(proto: "max_possible"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularDoubleField(value: &self.resolution) }() - case 2: try { try decoder.decodeSingularDoubleField(value: &self.maxPossible) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if self.resolution.bitPattern != 0 { - try visitor.visitSingularDoubleField(value: self.resolution, fieldNumber: 1) - } - if self.maxPossible.bitPattern != 0 { - try visitor.visitSingularDoubleField(value: self.maxPossible, fieldNumber: 2) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_HistogramParams, rhs: Grpc_Testing_HistogramParams) -> Bool { - if lhs.resolution != rhs.resolution {return false} - if lhs.maxPossible != rhs.maxPossible {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_HistogramData: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".HistogramData" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "bucket"), - 2: .standard(proto: "min_seen"), - 3: .standard(proto: "max_seen"), - 4: .same(proto: "sum"), - 5: .standard(proto: "sum_of_squares"), - 6: .same(proto: "count"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeRepeatedUInt32Field(value: &self.bucket) }() - case 2: try { try decoder.decodeSingularDoubleField(value: &self.minSeen) }() - case 3: try { try decoder.decodeSingularDoubleField(value: &self.maxSeen) }() - case 4: try { try decoder.decodeSingularDoubleField(value: &self.sum) }() - case 5: try { try decoder.decodeSingularDoubleField(value: &self.sumOfSquares) }() - case 6: try { try decoder.decodeSingularDoubleField(value: &self.count) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if !self.bucket.isEmpty { - try visitor.visitPackedUInt32Field(value: self.bucket, fieldNumber: 1) - } - if self.minSeen.bitPattern != 0 { - try visitor.visitSingularDoubleField(value: self.minSeen, fieldNumber: 2) - } - if self.maxSeen.bitPattern != 0 { - try visitor.visitSingularDoubleField(value: self.maxSeen, fieldNumber: 3) - } - if self.sum.bitPattern != 0 { - try visitor.visitSingularDoubleField(value: self.sum, fieldNumber: 4) - } - if self.sumOfSquares.bitPattern != 0 { - try visitor.visitSingularDoubleField(value: self.sumOfSquares, fieldNumber: 5) - } - if self.count.bitPattern != 0 { - try visitor.visitSingularDoubleField(value: self.count, fieldNumber: 6) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_HistogramData, rhs: Grpc_Testing_HistogramData) -> Bool { - if lhs.bucket != rhs.bucket {return false} - if lhs.minSeen != rhs.minSeen {return false} - if lhs.maxSeen != rhs.maxSeen {return false} - if lhs.sum != rhs.sum {return false} - if lhs.sumOfSquares != rhs.sumOfSquares {return false} - if lhs.count != rhs.count {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_RequestResultCount: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".RequestResultCount" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "status_code"), - 2: .same(proto: "count"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularInt32Field(value: &self.statusCode) }() - case 2: try { try decoder.decodeSingularInt64Field(value: &self.count) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if self.statusCode != 0 { - try visitor.visitSingularInt32Field(value: self.statusCode, fieldNumber: 1) - } - if self.count != 0 { - try visitor.visitSingularInt64Field(value: self.count, fieldNumber: 2) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_RequestResultCount, rhs: Grpc_Testing_RequestResultCount) -> Bool { - if lhs.statusCode != rhs.statusCode {return false} - if lhs.count != rhs.count {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Testing_ClientStats: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".ClientStats" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "latencies"), - 2: .standard(proto: "time_elapsed"), - 3: .standard(proto: "time_user"), - 4: .standard(proto: "time_system"), - 5: .standard(proto: "request_results"), - 6: .standard(proto: "cq_poll_count"), - 7: .standard(proto: "core_stats"), - ] - - mutating func decodeMessage(decoder: inout D) throws { - while let fieldNumber = try decoder.nextFieldNumber() { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every case branch when no optimizations are - // enabled. https://github.com/apple/swift-protobuf/issues/1034 - switch fieldNumber { - case 1: try { try decoder.decodeSingularMessageField(value: &self._latencies) }() - case 2: try { try decoder.decodeSingularDoubleField(value: &self.timeElapsed) }() - case 3: try { try decoder.decodeSingularDoubleField(value: &self.timeUser) }() - case 4: try { try decoder.decodeSingularDoubleField(value: &self.timeSystem) }() - case 5: try { try decoder.decodeRepeatedMessageField(value: &self.requestResults) }() - case 6: try { try decoder.decodeSingularUInt64Field(value: &self.cqPollCount) }() - case 7: try { try decoder.decodeSingularMessageField(value: &self._coreStats) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - // The use of inline closures is to circumvent an issue where the compiler - // allocates stack space for every if/case branch local when no optimizations - // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and - // https://github.com/apple/swift-protobuf/issues/1182 - try { if let v = self._latencies { - try visitor.visitSingularMessageField(value: v, fieldNumber: 1) - } }() - if self.timeElapsed.bitPattern != 0 { - try visitor.visitSingularDoubleField(value: self.timeElapsed, fieldNumber: 2) - } - if self.timeUser.bitPattern != 0 { - try visitor.visitSingularDoubleField(value: self.timeUser, fieldNumber: 3) - } - if self.timeSystem.bitPattern != 0 { - try visitor.visitSingularDoubleField(value: self.timeSystem, fieldNumber: 4) - } - if !self.requestResults.isEmpty { - try visitor.visitRepeatedMessageField(value: self.requestResults, fieldNumber: 5) - } - if self.cqPollCount != 0 { - try visitor.visitSingularUInt64Field(value: self.cqPollCount, fieldNumber: 6) - } - try { if let v = self._coreStats { - try visitor.visitSingularMessageField(value: v, fieldNumber: 7) - } }() - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Testing_ClientStats, rhs: Grpc_Testing_ClientStats) -> Bool { - if lhs._latencies != rhs._latencies {return false} - if lhs.timeElapsed != rhs.timeElapsed {return false} - if lhs.timeUser != rhs.timeUser {return false} - if lhs.timeSystem != rhs.timeSystem {return false} - if lhs.requestResults != rhs.requestResults {return false} - if lhs.cqPollCount != rhs.cqPollCount {return false} - if lhs._coreStats != rhs._coreStats {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} diff --git a/Sources/performance-worker/Generated/grpc_testing_worker_service.grpc.swift b/Sources/performance-worker/Generated/grpc_testing_worker_service.grpc.swift deleted file mode 100644 index 58bad0ad0..000000000 --- a/Sources/performance-worker/Generated/grpc_testing_worker_service.grpc.swift +++ /dev/null @@ -1,235 +0,0 @@ -// Copyright 2015 gRPC authors. -// -// 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. - -/// An integration test service that covers all the method signature permutations -/// of unary/streaming requests/responses. - -// DO NOT EDIT. -// swift-format-ignore-file -// -// Generated by the gRPC Swift generator plugin for the protocol buffer compiler. -// Source: grpc/testing/worker_service.proto -// -// For information on using the generated types, please see the documentation: -// https://github.com/grpc/grpc-swift - -import GRPCCore -import GRPCProtobuf - -internal enum Grpc_Testing_WorkerService { - internal static let descriptor = GRPCCore.ServiceDescriptor.grpc_testing_WorkerService - internal enum Method { - internal enum RunServer { - internal typealias Input = Grpc_Testing_ServerArgs - internal typealias Output = Grpc_Testing_ServerStatus - internal static let descriptor = GRPCCore.MethodDescriptor( - service: Grpc_Testing_WorkerService.descriptor.fullyQualifiedService, - method: "RunServer" - ) - } - internal enum RunClient { - internal typealias Input = Grpc_Testing_ClientArgs - internal typealias Output = Grpc_Testing_ClientStatus - internal static let descriptor = GRPCCore.MethodDescriptor( - service: Grpc_Testing_WorkerService.descriptor.fullyQualifiedService, - method: "RunClient" - ) - } - internal enum CoreCount { - internal typealias Input = Grpc_Testing_CoreRequest - internal typealias Output = Grpc_Testing_CoreResponse - internal static let descriptor = GRPCCore.MethodDescriptor( - service: Grpc_Testing_WorkerService.descriptor.fullyQualifiedService, - method: "CoreCount" - ) - } - internal enum QuitWorker { - internal typealias Input = Grpc_Testing_Void - internal typealias Output = Grpc_Testing_Void - internal static let descriptor = GRPCCore.MethodDescriptor( - service: Grpc_Testing_WorkerService.descriptor.fullyQualifiedService, - method: "QuitWorker" - ) - } - internal static let descriptors: [GRPCCore.MethodDescriptor] = [ - RunServer.descriptor, - RunClient.descriptor, - CoreCount.descriptor, - QuitWorker.descriptor - ] - } - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - internal typealias StreamingServiceProtocol = Grpc_Testing_WorkerServiceStreamingServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - internal typealias ServiceProtocol = Grpc_Testing_WorkerServiceServiceProtocol -} - -extension GRPCCore.ServiceDescriptor { - internal static let grpc_testing_WorkerService = Self( - package: "grpc.testing", - service: "WorkerService" - ) -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -internal protocol Grpc_Testing_WorkerServiceStreamingServiceProtocol: GRPCCore.RegistrableRPCService { - /// Start server with specified workload. - /// First request sent specifies the ServerConfig followed by ServerStatus - /// response. After that, a "Mark" can be sent anytime to request the latest - /// stats. Closing the stream will initiate shutdown of the test server - /// and once the shutdown has finished, the OK status is sent to terminate - /// this RPC. - func runServer( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream - - /// Start client with specified workload. - /// First request sent specifies the ClientConfig followed by ClientStatus - /// response. After that, a "Mark" can be sent anytime to request the latest - /// stats. Closing the stream will initiate shutdown of the test client - /// and once the shutdown has finished, the OK status is sent to terminate - /// this RPC. - func runClient( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream - - /// Just return the core count - unary call - func coreCount( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream - - /// Quit this worker - func quitWorker( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream -} - -/// Conformance to `GRPCCore.RegistrableRPCService`. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension Grpc_Testing_WorkerService.StreamingServiceProtocol { - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - internal func registerMethods(with router: inout GRPCCore.RPCRouter) { - router.registerHandler( - forMethod: Grpc_Testing_WorkerService.Method.RunServer.descriptor, - deserializer: GRPCProtobuf.ProtobufDeserializer(), - serializer: GRPCProtobuf.ProtobufSerializer(), - handler: { request, context in - try await self.runServer( - request: request, - context: context - ) - } - ) - router.registerHandler( - forMethod: Grpc_Testing_WorkerService.Method.RunClient.descriptor, - deserializer: GRPCProtobuf.ProtobufDeserializer(), - serializer: GRPCProtobuf.ProtobufSerializer(), - handler: { request, context in - try await self.runClient( - request: request, - context: context - ) - } - ) - router.registerHandler( - forMethod: Grpc_Testing_WorkerService.Method.CoreCount.descriptor, - deserializer: GRPCProtobuf.ProtobufDeserializer(), - serializer: GRPCProtobuf.ProtobufSerializer(), - handler: { request, context in - try await self.coreCount( - request: request, - context: context - ) - } - ) - router.registerHandler( - forMethod: Grpc_Testing_WorkerService.Method.QuitWorker.descriptor, - deserializer: GRPCProtobuf.ProtobufDeserializer(), - serializer: GRPCProtobuf.ProtobufSerializer(), - handler: { request, context in - try await self.quitWorker( - request: request, - context: context - ) - } - ) - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -internal protocol Grpc_Testing_WorkerServiceServiceProtocol: Grpc_Testing_WorkerService.StreamingServiceProtocol { - /// Start server with specified workload. - /// First request sent specifies the ServerConfig followed by ServerStatus - /// response. After that, a "Mark" can be sent anytime to request the latest - /// stats. Closing the stream will initiate shutdown of the test server - /// and once the shutdown has finished, the OK status is sent to terminate - /// this RPC. - func runServer( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream - - /// Start client with specified workload. - /// First request sent specifies the ClientConfig followed by ClientStatus - /// response. After that, a "Mark" can be sent anytime to request the latest - /// stats. Closing the stream will initiate shutdown of the test client - /// and once the shutdown has finished, the OK status is sent to terminate - /// this RPC. - func runClient( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream - - /// Just return the core count - unary call - func coreCount( - request: GRPCCore.ServerRequest.Single, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Single - - /// Quit this worker - func quitWorker( - request: GRPCCore.ServerRequest.Single, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Single -} - -/// Partial conformance to `Grpc_Testing_WorkerServiceStreamingServiceProtocol`. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension Grpc_Testing_WorkerService.ServiceProtocol { - internal func coreCount( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream { - let response = try await self.coreCount( - request: GRPCCore.ServerRequest.Single(stream: request), - context: context - ) - return GRPCCore.ServerResponse.Stream(single: response) - } - - internal func quitWorker( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream { - let response = try await self.quitWorker( - request: GRPCCore.ServerRequest.Single(stream: request), - context: context - ) - return GRPCCore.ServerResponse.Stream(single: response) - } -} \ No newline at end of file diff --git a/Sources/performance-worker/Generated/grpc_testing_worker_service.pb.swift b/Sources/performance-worker/Generated/grpc_testing_worker_service.pb.swift deleted file mode 100644 index 73f9c0029..000000000 --- a/Sources/performance-worker/Generated/grpc_testing_worker_service.pb.swift +++ /dev/null @@ -1,28 +0,0 @@ -// DO NOT EDIT. -// swift-format-ignore-file -// swiftlint:disable all -// -// Generated by the Swift generator plugin for the protocol buffer compiler. -// Source: grpc/testing/worker_service.proto -// -// For information on using the generated types, please see the documentation: -// https://github.com/apple/swift-protobuf/ - -// Copyright 2015 gRPC authors. -// -// 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. - -/// An integration test service that covers all the method signature permutations -/// of unary/streaming requests/responses. - -// This file contained no messages, enums, or extensions. diff --git a/Sources/performance-worker/PerformanceWorker.swift b/Sources/performance-worker/PerformanceWorker.swift deleted file mode 100644 index 83c9f7e82..000000000 --- a/Sources/performance-worker/PerformanceWorker.swift +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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 ArgumentParser -import GRPCCore -import GRPCHTTP2Core -import GRPCHTTP2TransportNIOPosix -import NIOPosix - -@main -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -struct PerformanceWorker: AsyncParsableCommand { - static var configuration: CommandConfiguration { - CommandConfiguration( - commandName: "performance-worker", - discussion: """ - This program starts a gRPC server running the 'worker' service. The worker service is \ - instructed by a driver program to become a benchmark client or a benchmark server. - - Typically at least two workers are started (at least one server and one client), and the \ - driver instructs benchmark clients to execute various scenarios against benchmark servers. \ - Results are reported back to the driver once scenarios have been completed. - - See https://grpc.io/docs/guides/benchmarking for more details. - """ - ) - } - - @Option( - name: .customLong("driver_port"), - help: "Port to listen on for connections from the driver." - ) - var driverPort: Int - - func run() async throws { - debugOnly { - print("[WARNING] performance-worker built in DEBUG mode, results won't be representative.") - } - - let server = GRPCServer( - transport: .http2NIOPosix( - address: .ipv4(host: "127.0.0.1", port: self.driverPort), - config: .defaults(transportSecurity: .plaintext) - ), - services: [WorkerService()] - ) - try await server.serve() - } -} - -private func debugOnly(_ body: () -> Void) { - assert(alwaysTrue(body)) -} - -private func alwaysTrue(_ body: () -> Void) -> Bool { - body() - return true -} diff --git a/Sources/performance-worker/RPCStats.swift b/Sources/performance-worker/RPCStats.swift deleted file mode 100644 index bc2bba74b..000000000 --- a/Sources/performance-worker/RPCStats.swift +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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 -import GRPCCore -import NIOConcurrencyHelpers - -/// Stores the real time latency histogram and error code count dictionary, -/// for the RPCs made by a particular GRPCClient. It gets updated after -/// each finished RPC. -/// -/// The time latency is measured in nanoseconds. -struct RPCStats { - var latencyHistogram: LatencyHistogram - var requestResultCount: [RPCError.Code: Int64] - - init(latencyHistogram: LatencyHistogram, requestResultCount: [RPCError.Code: Int64] = [:]) { - self.latencyHistogram = latencyHistogram - self.requestResultCount = requestResultCount - } - - /// Histograms are stored with exponentially increasing bucket sizes. - /// The first bucket is [0, `multiplier`) where `multiplier` = 1 + resolution - /// Bucket n (n>=1) contains [`multiplier`**n, `multiplier`**(n+1)) - /// There are sufficient buckets to reach max_bucket_start - struct LatencyHistogram { - var sum: Double - var sumOfSquares: Double - var countOfValuesSeen: Double - var multiplier: Double - var oneOnLogMultiplier: Double - var minSeen: Double - var maxSeen: Double - var maxPossible: Double - var buckets: [UInt32] - - /// Initialise a histogram. - /// - parameters: - /// - resolution: Defines the width of the buckets - see the description of this structure. - /// - maxBucketStart: Defines the start of the greatest valued bucket. - init(resolution: Double = 0.01, maxBucketStart: Double = 60e9) { - precondition(resolution > 0.0) - precondition(maxBucketStart > resolution) - self.sum = 0.0 - self.sumOfSquares = 0.0 - self.multiplier = 1.0 + resolution - self.oneOnLogMultiplier = 1.0 / log(1.0 + resolution) - self.maxPossible = maxBucketStart - self.countOfValuesSeen = 0.0 - self.minSeen = maxBucketStart - self.maxSeen = 0.0 - let numBuckets = - LatencyHistogram.uncheckedBucket( - forValue: maxBucketStart, - oneOnLogMultiplier: self.oneOnLogMultiplier - ) + 1 - precondition(numBuckets > 1) - precondition(numBuckets < 100_000_000) - self.buckets = .init(repeating: 0, count: numBuckets) - } - - struct HistorgramShapeMismatch: Error {} - - /// Determine a bucket index given a value - does no bounds checking - private static func uncheckedBucket(forValue value: Double, oneOnLogMultiplier: Double) -> Int { - return Int(log(value) * oneOnLogMultiplier) - } - - private func bucket(forValue value: Double) -> Int { - let bucket = LatencyHistogram.uncheckedBucket( - forValue: min(self.maxPossible, max(0, value)), - oneOnLogMultiplier: self.oneOnLogMultiplier - ) - assert(bucket < self.buckets.count) - assert(bucket >= 0) - return bucket - } - - /// Add a value to this histogram, updating buckets and stats - /// - parameters: - /// - value: The value to add. - public mutating func record(_ value: Double) { - self.sum += value - self.sumOfSquares += value * value - self.countOfValuesSeen += 1 - if value < self.minSeen { - self.minSeen = value - } - if value > self.maxSeen { - self.maxSeen = value - } - self.buckets[self.bucket(forValue: value)] += 1 - } - - /// Merge two histograms together updating `self` - /// - parameters: - /// - other: the other histogram to merge into this. - public mutating func merge(_ other: LatencyHistogram) throws { - guard (self.buckets.count == other.buckets.count) || (self.multiplier == other.multiplier) - else { - // Fail because these histograms don't match. - throw HistorgramShapeMismatch() - } - - self.sum += other.sum - self.sumOfSquares += other.sumOfSquares - self.countOfValuesSeen += other.countOfValuesSeen - if other.minSeen < self.minSeen { - self.minSeen = other.minSeen - } - if other.maxSeen > self.maxSeen { - self.maxSeen = other.maxSeen - } - for bucket in 0 ..< self.buckets.count { - self.buckets[bucket] += other.buckets[bucket] - } - } - } - - @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) - mutating func merge(_ other: RPCStats) throws { - try self.latencyHistogram.merge(other.latencyHistogram) - self.requestResultCount.merge(other.requestResultCount) { (current, new) in - current + new - } - } -} diff --git a/Sources/performance-worker/ResourceUsage.swift b/Sources/performance-worker/ResourceUsage.swift deleted file mode 100644 index 7582a8328..000000000 --- a/Sources/performance-worker/ResourceUsage.swift +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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 Dispatch -import NIOCore -import NIOFileSystem - -#if canImport(Darwin) -import Darwin -#elseif canImport(Musl) -import Musl -#elseif canImport(Glibc) -import Glibc -#else -let badOS = { fatalError("unsupported OS") }() -#endif - -#if canImport(Darwin) -private let OUR_RUSAGE_SELF: Int32 = RUSAGE_SELF -#elseif canImport(Musl) || canImport(Glibc) -private let OUR_RUSAGE_SELF: Int32 = RUSAGE_SELF.rawValue -#endif - -/// Client resource usage stats. -@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) -internal struct ClientStats: Sendable { - var time: Double - var userTime: Double - var systemTime: Double - - init( - time: Double, - userTime: Double, - systemTime: Double - ) { - self.time = time - self.userTime = userTime - self.systemTime = systemTime - } - - init() { - self.time = Double(DispatchTime.now().uptimeNanoseconds) * 1e-9 - if let usage = System.resourceUsage() { - self.userTime = Double(usage.ru_utime.tv_sec) + Double(usage.ru_utime.tv_usec) * 1e-6 - self.systemTime = Double(usage.ru_stime.tv_sec) + Double(usage.ru_stime.tv_usec) * 1e-6 - } else { - self.userTime = 0 - self.systemTime = 0 - } - } - - internal func difference(to state: ClientStats) -> ClientStats { - return ClientStats( - time: self.time - state.time, - userTime: self.userTime - state.userTime, - systemTime: self.systemTime - state.systemTime - ) - } -} - -/// Server resource usage stats. -@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) -internal struct ServerStats: Sendable { - var time: Double - var userTime: Double - var systemTime: Double - var totalCPUTime: UInt64 - var idleCPUTime: UInt64 - - init( - time: Double, - userTime: Double, - systemTime: Double, - totalCPUTime: UInt64, - idleCPUTime: UInt64 - ) { - self.time = time - self.userTime = userTime - self.systemTime = systemTime - self.totalCPUTime = totalCPUTime - self.idleCPUTime = idleCPUTime - } - - init() async throws { - self.time = Double(DispatchTime.now().uptimeNanoseconds) * 1e-9 - if let usage = System.resourceUsage() { - self.userTime = Double(usage.ru_utime.tv_sec) + Double(usage.ru_utime.tv_usec) * 1e-6 - self.systemTime = Double(usage.ru_stime.tv_sec) + Double(usage.ru_stime.tv_usec) * 1e-6 - } else { - self.userTime = 0 - self.systemTime = 0 - } - let (totalCPUTime, idleCPUTime) = try await ServerStats.getTotalAndIdleCPUTime() - self.totalCPUTime = totalCPUTime - self.idleCPUTime = idleCPUTime - } - - internal func difference(to stats: ServerStats) -> ServerStats { - return ServerStats( - time: self.time - stats.time, - userTime: self.userTime - stats.userTime, - systemTime: self.systemTime - stats.systemTime, - totalCPUTime: self.totalCPUTime - stats.totalCPUTime, - idleCPUTime: self.idleCPUTime - stats.idleCPUTime - ) - } - - /// Computes the total and idle CPU time after extracting stats from the first line of '/proc/stat'. - /// - /// The first line in '/proc/stat' file looks as follows: - /// CPU [user] [nice] [system] [idle] [iowait] [irq] [softirq] - /// The totalCPUTime is computed as follows: - /// total = user + nice + system + idle - private static func getTotalAndIdleCPUTime() async throws -> ( - totalCPUTime: UInt64, idleCPUTime: UInt64 - ) { - #if os(macOS) || os(iOS) || os(tvOS) || os(watchOS) || os(Linux) || os(Android) - let contents: ByteBuffer - do { - contents = try await ByteBuffer( - contentsOf: "/proc/stat", - maximumSizeAllowed: .kilobytes(20) - ) - } catch { - return (0, 0) - } - - let view = contents.readableBytesView - guard let firstNewLineIndex = view.firstIndex(of: UInt8(ascii: "\n")) else { - return (0, 0) - } - let firstLine = String(buffer: ByteBuffer(view[0 ... firstNewLineIndex])) - - let lineComponents = firstLine.components(separatedBy: " ") - if lineComponents.count < 5 || lineComponents[0] != "CPU" { - return (0, 0) - } - - let CPUTime: [UInt64] = lineComponents[1 ... 4].compactMap { UInt64($0) } - if CPUTime.count < 4 { - return (0, 0) - } - - let totalCPUTime = CPUTime.reduce(0, +) - return (totalCPUTime, CPUTime[3]) - - #else - return (0, 0) - #endif - } -} - -extension System { - fileprivate static func resourceUsage() -> rusage? { - var usage = rusage() - - if getrusage(OUR_RUSAGE_SELF, &usage) == 0 { - return usage - } else { - return nil - } - } -} diff --git a/Sources/performance-worker/WorkerService.swift b/Sources/performance-worker/WorkerService.swift deleted file mode 100644 index 945dca3a7..000000000 --- a/Sources/performance-worker/WorkerService.swift +++ /dev/null @@ -1,584 +0,0 @@ -/* - * Copyright 2024, gRPC Authors All rights reserved. - * - * 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 GRPCCore -import GRPCHTTP2Core -import GRPCHTTP2TransportNIOPosix -import NIOConcurrencyHelpers -import NIOCore -import NIOPosix - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -final class WorkerService: Sendable { - private let state: NIOLockedValueBox - - init() { - self.state = NIOLockedValueBox(State()) - } - - private struct State { - private var role: Role - - enum Role { - case none - case client(Client) - case server(Server) - } - - struct Server { - var server: GRPCServer - var stats: ServerStats - var eventLoopGroup: MultiThreadedEventLoopGroup - } - - struct Client { - var clients: [BenchmarkClient] - var stats: ClientStats - var rpcStats: RPCStats - } - - init() { - self.role = .none - } - - mutating func collectServerStats(replaceWith newStats: ServerStats? = nil) -> ServerStats? { - switch self.role { - case var .server(serverState): - let stats = serverState.stats - if let newStats = newStats { - serverState.stats = newStats - self.role = .server(serverState) - } - return stats - case .client, .none: - return nil - } - } - - mutating func collectClientStats( - replaceWith newStats: ClientStats? = nil - ) -> (ClientStats, RPCStats)? { - switch self.role { - case var .client(state): - // Grab the existing stats and update if necessary. - let stats = state.stats - if let newStats = newStats { - state.stats = newStats - } - - // Merge in RPC stats from each client. - for client in state.clients { - try? state.rpcStats.merge(client.currentStats) - } - - self.role = .client(state) - return (stats, state.rpcStats) - - case .server, .none: - return nil - } - } - - enum OnStartedServer { - case runServer - case invalidState(RPCError) - } - - mutating func startedServer( - _ server: GRPCServer, - stats: ServerStats, - eventLoopGroup: MultiThreadedEventLoopGroup - ) -> OnStartedServer { - let action: OnStartedServer - - switch self.role { - case .none: - let state = State.Server(server: server, stats: stats, eventLoopGroup: eventLoopGroup) - self.role = .server(state) - action = .runServer - case .server: - let error = RPCError(code: .alreadyExists, message: "A server has already been set up.") - action = .invalidState(error) - case .client: - let error = RPCError(code: .failedPrecondition, message: "This worker has a client setup.") - action = .invalidState(error) - } - - return action - } - - enum OnStartedClients { - case runClients - case invalidState(RPCError) - } - - mutating func startedClients( - _ clients: [BenchmarkClient], - stats: ClientStats, - rpcStats: RPCStats - ) -> OnStartedClients { - let action: OnStartedClients - - switch self.role { - case .none: - let state = State.Client(clients: clients, stats: stats, rpcStats: rpcStats) - self.role = .client(state) - action = .runClients - case .server: - let error = RPCError(code: .alreadyExists, message: "This worker has a server setup.") - action = .invalidState(error) - case .client: - let error = RPCError( - code: .failedPrecondition, - message: "Clients have already been set up." - ) - action = .invalidState(error) - } - - return action - } - - enum OnServerShutDown { - case shutdown(MultiThreadedEventLoopGroup) - case nothing - } - - mutating func serverShutdown() -> OnServerShutDown { - switch self.role { - case .client: - preconditionFailure("Invalid state") - case .server(let state): - self.role = .none - return .shutdown(state.eventLoopGroup) - case .none: - return .nothing - } - } - - enum OnStopListening { - case stopListening(GRPCServer) - case nothing - } - - func stopListening() -> OnStopListening { - switch self.role { - case .client: - preconditionFailure("Invalid state") - case .server(let state): - return .stopListening(state.server) - case .none: - return .nothing - } - } - - enum OnCloseClient { - case close([BenchmarkClient]) - case nothing - } - - mutating func closeClients() -> OnCloseClient { - switch self.role { - case .client(let state): - self.role = .none - return .close(state.clients) - case .server: - preconditionFailure("Invalid state") - case .none: - return .nothing - } - } - - enum OnQuitWorker { - case shutDownServer(GRPCServer) - case shutDownClients([BenchmarkClient]) - case nothing - } - - mutating func quit() -> OnQuitWorker { - switch self.role { - case .none: - return .nothing - case .client(let state): - self.role = .none - return .shutDownClients(state.clients) - case .server(let state): - self.role = .none - return .shutDownServer(state.server) - } - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension WorkerService: Grpc_Testing_WorkerService.ServiceProtocol { - func quitWorker( - request: ServerRequest.Single, - context: ServerContext - ) async throws -> ServerResponse.Single { - let onQuit = self.state.withLockedValue { $0.quit() } - - switch onQuit { - case .nothing: - () - - case .shutDownClients(let clients): - for client in clients { - client.shutdown() - } - - case .shutDownServer(let server): - server.beginGracefulShutdown() - } - - return ServerResponse.Single(message: Grpc_Testing_Void()) - } - - func coreCount( - request: ServerRequest.Single, - context: ServerContext - ) async throws -> ServerResponse.Single { - let coreCount = System.coreCount - return ServerResponse.Single( - message: Grpc_Testing_WorkerService.Method.CoreCount.Output.with { - $0.cores = Int32(coreCount) - } - ) - } - - func runServer( - request: ServerRequest.Stream, - context: ServerContext - ) async throws -> ServerResponse.Stream { - return ServerResponse.Stream { writer in - try await withThrowingTaskGroup(of: Void.self) { group in - for try await message in request.messages { - switch message.argtype { - case let .some(.setup(serverConfig)): - let (server, transport) = try await self.startServer(serverConfig) - group.addTask { - let result: Result - - do { - try await server.serve() - result = .success(()) - } catch { - result = .failure(error) - } - - switch self.state.withLockedValue({ $0.serverShutdown() }) { - case .shutdown(let eventLoopGroup): - try await eventLoopGroup.shutdownGracefully() - case .nothing: - () - } - - try result.get() - } - - // Wait for the server to bind. - let address = try await transport.listeningAddress - - let port: Int - if let ipv4 = address.ipv4 { - port = ipv4.port - } else if let ipv6 = address.ipv6 { - port = ipv6.port - } else { - throw RPCError( - code: .internalError, - message: "Server listening on unsupported address '\(address)'" - ) - } - - // Tell the client what port the server is listening on. - let message = Grpc_Testing_ServerStatus.with { $0.port = Int32(port) } - try await writer.write(message) - - case let .some(.mark(mark)): - let response = try await self.makeServerStatsResponse(reset: mark.reset) - try await writer.write(response) - - case .none: - () - } - } - - // Request stream ended, tell the server to stop listening. Once it's finished it will - // shutdown its ELG. - switch self.state.withLockedValue({ $0.stopListening() }) { - case .stopListening(let server): - server.beginGracefulShutdown() - case .nothing: - () - } - } - - return [:] - } - } - - func runClient( - request: ServerRequest.Stream, - context: ServerContext - ) async throws -> ServerResponse.Stream { - return ServerResponse.Stream { writer in - try await withThrowingTaskGroup(of: Void.self) { group in - for try await message in request.messages { - switch message.argtype { - case let .setup(config): - // Create the clients with the initial stats. - let clients = try await self.setupClients(config) - - for client in clients { - group.addTask { - try await client.run() - } - } - - let message = try await self.makeClientStatsResponse(reset: false) - try await writer.write(message) - - case let .mark(mark): - let response = try await self.makeClientStatsResponse(reset: mark.reset) - try await writer.write(response) - - case .none: - () - } - } - - switch self.state.withLockedValue({ $0.closeClients() }) { - case .close(let clients): - for client in clients { - client.shutdown() - } - case .nothing: - () - } - - try await group.waitForAll() - - return [:] - } - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension WorkerService { - private func startServer( - _ serverConfig: Grpc_Testing_ServerConfig - ) async throws -> (GRPCServer, HTTP2ServerTransport.Posix) { - // Prepare an ELG, the test might require more than the default of one. - let numberOfThreads: Int - if serverConfig.asyncServerThreads > 0 { - numberOfThreads = Int(serverConfig.asyncServerThreads) - } else { - numberOfThreads = System.coreCount - } - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: numberOfThreads) - - // Don't restrict the max payload size, the client is always trusted. - var config = HTTP2ServerTransport.Posix.Config.defaults(transportSecurity: .plaintext) - config.rpc.maxRequestPayloadSize = .max - - let transport = HTTP2ServerTransport.Posix( - address: .ipv4(host: "127.0.0.1", port: Int(serverConfig.port)), - config: config, - eventLoopGroup: eventLoopGroup - ) - - let server = GRPCServer(transport: transport, services: [BenchmarkService()]) - let stats = try await ServerStats() - - // Hold on to the server and ELG in the state machine. - let action = self.state.withLockedValue { - $0.startedServer(server, stats: stats, eventLoopGroup: eventLoopGroup) - } - - switch action { - case .runServer: - return (server, transport) - case .invalidState(let error): - server.beginGracefulShutdown() - try await eventLoopGroup.shutdownGracefully() - throw error - } - } - - private func makeServerStatsResponse( - reset: Bool - ) async throws -> Grpc_Testing_WorkerService.Method.RunServer.Output { - let currentStats = try await ServerStats() - let initialStats = self.state.withLockedValue { state in - return state.collectServerStats(replaceWith: reset ? currentStats : nil) - } - - guard let initialStats = initialStats else { - throw RPCError( - code: .notFound, - message: "There are no initial server stats. A server must be setup before calling 'mark'." - ) - } - - let differences = currentStats.difference(to: initialStats) - return Grpc_Testing_WorkerService.Method.RunServer.Output.with { - $0.stats = Grpc_Testing_ServerStats.with { - $0.idleCpuTime = differences.idleCPUTime - $0.timeElapsed = differences.time - $0.timeSystem = differences.systemTime - $0.timeUser = differences.userTime - $0.totalCpuTime = differences.totalCPUTime - } - } - } - - private func setupClients(_ config: Grpc_Testing_ClientConfig) async throws -> [BenchmarkClient] { - guard let rpcType = BenchmarkClient.RPCType(config.rpcType) else { - throw RPCError(code: .invalidArgument, message: "Unknown RPC type") - } - - // Parse the server targets into resolvable targets. - let ipv4Addresses = try self.parseServerTargets(config.serverTargets) - let target = ResolvableTargets.IPv4(addresses: ipv4Addresses) - - var clients = [BenchmarkClient]() - for _ in 0 ..< config.clientChannels { - let client = BenchmarkClient( - client: GRPCClient( - transport: try .http2NIOPosix( - target: target, - config: .defaults(transportSecurity: .plaintext) - ) - ), - concurrentRPCs: Int(config.outstandingRpcsPerChannel), - rpcType: rpcType, - messagesPerStream: Int(config.messagesPerStream), - protoParams: config.payloadConfig.simpleParams, - histogramParams: config.histogramParams - ) - - clients.append(client) - } - - let stats = ClientStats() - let histogram = RPCStats.LatencyHistogram( - resolution: config.histogramParams.resolution, - maxBucketStart: config.histogramParams.maxPossible - ) - let rpcStats = RPCStats(latencyHistogram: histogram) - - let action = self.state.withLockedValue { state in - state.startedClients(clients, stats: stats, rpcStats: rpcStats) - } - - switch action { - case .runClients: - return clients - case .invalidState(let error): - for client in clients { - client.shutdown() - } - throw error - } - } - - private func parseServerTarget(_ target: String) -> GRPCHTTP2Core.SocketAddress.IPv4? { - guard let index = target.firstIndex(of: ":") else { return nil } - - let host = target[.. [GRPCHTTP2Core.SocketAddress.IPv4] { - try targets.map { target in - if let ipv4 = self.parseServerTarget(target) { - return ipv4 - } else { - throw RPCError( - code: .invalidArgument, - message: """ - Couldn't parse target '\(target)'. Must be in the format ':' for IPv4 \ - or '[]:' for IPv6. - """ - ) - } - } - } - - private func makeClientStatsResponse( - reset: Bool - ) async throws -> Grpc_Testing_WorkerService.Method.RunClient.Output { - let currentUsageStats = ClientStats() - - let stats = self.state.withLockedValue { state in - state.collectClientStats(replaceWith: reset ? currentUsageStats : nil) - } - - guard let (initialUsageStats, rpcStats) = stats else { - throw RPCError( - code: .notFound, - message: "There are no initial client stats. Clients must be setup before calling 'mark'." - ) - } - - let differences = currentUsageStats.difference(to: initialUsageStats) - - let requestResults = rpcStats.requestResultCount.map { (key, value) in - return Grpc_Testing_RequestResultCount.with { - $0.statusCode = Int32(key.rawValue) - $0.count = value - } - } - - return Grpc_Testing_WorkerService.Method.RunClient.Output.with { - $0.stats = Grpc_Testing_ClientStats.with { - $0.timeElapsed = differences.time - $0.timeSystem = differences.systemTime - $0.timeUser = differences.userTime - $0.requestResults = requestResults - $0.latencies = Grpc_Testing_HistogramData.with { - $0.bucket = rpcStats.latencyHistogram.buckets - $0.minSeen = rpcStats.latencyHistogram.minSeen - $0.maxSeen = rpcStats.latencyHistogram.maxSeen - $0.sum = rpcStats.latencyHistogram.sum - $0.sumOfSquares = rpcStats.latencyHistogram.sumOfSquares - $0.count = rpcStats.latencyHistogram.countOfValuesSeen - } - } - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension BenchmarkClient.RPCType { - init?(_ rpcType: Grpc_Testing_RpcType) { - switch rpcType { - case .unary: - self = .unary - case .streaming: - self = .streaming - default: - return nil - } - } -} diff --git a/Sources/protoc-gen-grpc-swift/GenerateGRPC.swift b/Sources/protoc-gen-grpc-swift/GenerateGRPC.swift index 7d75b9d43..3e8930f91 100644 --- a/Sources/protoc-gen-grpc-swift/GenerateGRPC.swift +++ b/Sources/protoc-gen-grpc-swift/GenerateGRPC.swift @@ -18,11 +18,6 @@ import Foundation import SwiftProtobuf import SwiftProtobufPluginLibrary -#if compiler(>=6.0) -import GRPCCodeGen -import GRPCProtobufCodeGen -#endif - @main final class GenerateGRPC: CodeGenerator { var version: String? { @@ -66,15 +61,7 @@ final class GenerateGRPC: CodeGenerator { } if options.generateClient || options.generateServer || options.generateTestClient { - #if compiler(>=6.0) - if options.v2 { - try self.generateV2Stubs(descriptor, options: options, outputs: outputs) - } else { - try self.generateV1Stubs(descriptor, options: options, outputs: outputs) - } - #else try self.generateV1Stubs(descriptor, options: options, outputs: outputs) - #endif } } } @@ -111,29 +98,6 @@ final class GenerateGRPC: CodeGenerator { let fileGenerator = Generator(descriptor, options: options) try outputs.add(fileName: fileName, contents: fileGenerator.code) } - - #if compiler(>=6.0) - private func generateV2Stubs( - _ descriptor: FileDescriptor, - options: GeneratorOptions, - outputs: any GeneratorOutputs - ) throws { - let fileName = self.uniqueOutputFileName( - fileDescriptor: descriptor, - fileNamingOption: options.fileNaming - ) - - let config = SourceGenerator.Config(options: options) - let fileGenerator = ProtobufCodeGenerator(configuration: config) - let contents = try fileGenerator.generateCode( - from: descriptor, - protoFileModuleMappings: options.protoToModuleMappings, - extraModuleImports: options.extraModuleImports - ) - - try outputs.add(fileName: fileName, contents: contents) - } - #endif } extension GenerateGRPC { @@ -210,26 +174,3 @@ private func splitPath(pathname: String) -> (dir: String, base: String, suffix: } return (dir: dir, base: base, suffix: suffix) } - -#if compiler(>=6.0) -extension SourceGenerator.Config { - init(options: GeneratorOptions) { - let accessLevel: SourceGenerator.Config.AccessLevel - switch options.visibility { - case .internal: - accessLevel = .internal - case .package: - accessLevel = .package - case .public: - accessLevel = .public - } - - self.init( - accessLevel: accessLevel, - accessLevelOnImports: options.useAccessLevelOnImports, - client: options.generateClient, - server: options.generateServer - ) - } -} -#endif diff --git a/Sources/protoc-gen-grpc-swift/Options.swift b/Sources/protoc-gen-grpc-swift/Options.swift index 2b0f96d46..518a10d20 100644 --- a/Sources/protoc-gen-grpc-swift/Options.swift +++ b/Sources/protoc-gen-grpc-swift/Options.swift @@ -23,6 +23,8 @@ enum GenerationError: Error { case invalidParameterValue(name: String, value: String) /// Raised to wrap another error but provide a context message. case wrappedError(message: String, error: Error) + /// v2 isn't supported. + case unsupportedV2 var localizedDescription: String { switch self { @@ -32,6 +34,8 @@ enum GenerationError: Error { return "Unknown value for generation parameter '\(name)': '\(value)'" case let .wrappedError(message, error): return "\(message): \(error.localizedDescription)" + case .unsupportedV2: + return "v2 isn't supported by this version of protoc-gen-grpc-swift, see https://github.com/grpc/grpc-swift-protobuf" } } } @@ -171,9 +175,13 @@ struct GeneratorOptions { #if compiler(>=6.0) case "_V2": if let value = Bool(pair.value) { - self.v2 = value + if value { + throw GenerationError.unsupportedV2 + } else { + // _V2 is false, ignore it. + } } else { - throw GenerationError.invalidParameterValue(name: pair.key, value: pair.value) + throw GenerationError.unsupportedV2 } #endif