diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f8c873fd0..c03b8b7c6 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -108,15 +108,6 @@ jobs: run: ./Performance/allocations/test-allocation-counts.sh env: ${{ matrix.env }} timeout-minutes: 20 - - name: Install jemalloc for benchmarking - if: ${{ matrix.swift-version == '6.0' || matrix.swift-version == 'main' }} - run: apt update && apt-get install -y libjemalloc-dev - timeout-minutes: 20 - - name: Run Benchmarks - if: ${{ matrix.swift-version == '6.0' || matrix.swift-version == 'main' }} - working-directory: ./Performance/Benchmarks - run: swift package benchmark baseline check --no-progress --check-absolute-path Thresholds/${{ matrix.swift-version }}/ - timeout-minutes: 20 integration-tests: strategy: fail-fast: false @@ -124,16 +115,12 @@ jobs: include: - image: swiftlang/swift:nightly-jammy swift-tools-version: '6.0' - supports-v2: true - image: swift:6.0-jammy swift-tools-version: '6.0' - supports-v2: true - image: swift:5.10.1-noble swift-tools-version: '5.10' - supports-v2: false - image: swift:5.9-jammy swift-tools-version: '5.9' - supports-v2: false name: Integration Tests on ${{ matrix.image }} runs-on: ubuntu-latest container: @@ -144,9 +131,6 @@ jobs: run: apt update && apt install -y protobuf-compiler - name: SwiftPM plugin test (v1) run: ./scripts/run-plugin-tests.sh ${{ matrix.swift-tools-version }} "v1" - - name: SwiftPM plugin test (v2) - if: ${{ matrix.supports-v2 }} - run: ./scripts/run-plugin-tests.sh ${{ matrix.swift-tools-version }} "v2" - name: Build without NIOSSL run: swift build env: 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 4d2261212..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.24.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/Performance/Benchmarks/Benchmarks/GRPCSwiftBenchmark/Benchmarks.swift b/Performance/Benchmarks/Benchmarks/GRPCSwiftBenchmark/Benchmarks.swift deleted file mode 100644 index 827f12bdf..000000000 --- a/Performance/Benchmarks/Benchmarks/GRPCSwiftBenchmark/Benchmarks.swift +++ /dev/null @@ -1,140 +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. - */ -import Benchmark -import GRPCCore - -let benchmarks = { - Benchmark.defaultConfiguration = .init( - metrics: [ - .mallocCountTotal, - .syscalls, - .readSyscalls, - .writeSyscalls, - .memoryLeaked, - .retainCount, - .releaseCount, - ] - ) - - // async code is currently still quite flaky in the number of retain/release it does so we don't measure them today - var config = Benchmark.defaultConfiguration - config.metrics.removeAll(where: { $0 == .retainCount || $0 == .releaseCount }) - // Let p90 be off by 50. Benchmarks should do 1000s of iterations so this is small enough. - config.thresholds = [.mallocCountTotal: BenchmarkThresholds(absolute: [.p90: 50])] - - Benchmark("Metadata_Add_string", configuration: config) { benchmark in - for _ in benchmark.scaledIterations { - var metadata = Metadata() - for i in 0 ..< 1000 { - metadata.addString("\(i)", forKey: "\(i)") - } - } - } - - Benchmark("Metadata_Add_binary", configuration: config) { benchmark in - let value: [UInt8] = [1, 2, 3] - for _ in benchmark.scaledIterations { - var metadata = Metadata() - - benchmark.startMeasurement() - for i in 0 ..< 1000 { - metadata.addBinary(value, forKey: "\(i)") - } - benchmark.stopMeasurement() - } - } - - Benchmark("Metadata_Remove_values_for_key", configuration: config) { benchmark in - for _ in benchmark.scaledIterations { - var metadata = Metadata() - for i in 0 ..< 1000 { - metadata.addString("value", forKey: "\(i)") - } - - benchmark.startMeasurement() - for i in 0 ..< 1000 { - metadata.removeAllValues(forKey: "\(i)") - } - benchmark.stopMeasurement() - } - } - - Benchmark("Metadata_Iterate_all_values", configuration: config) { benchmark in - for _ in benchmark.scaledIterations { - var metadata = Metadata() - for i in 0 ..< 1000 { - metadata.addString("value", forKey: "key") - } - - benchmark.startMeasurement() - for value in metadata["key"] { - blackHole(value) - } - benchmark.stopMeasurement() - } - } - - Benchmark("Metadata_Iterate_string_values", configuration: config) { benchmark in - for _ in benchmark.scaledIterations { - var metadata = Metadata() - for i in 0 ..< 1000 { - metadata.addString("\(i)", forKey: "key") - } - - benchmark.startMeasurement() - for value in metadata[stringValues: "key"] { - blackHole(value) - } - benchmark.stopMeasurement() - } - } - - Benchmark( - "Metadata_Iterate_binary_values_when_only_binary_values_stored", - configuration: config - ) { benchmark in - for _ in benchmark.scaledIterations { - var metadata = Metadata() - for i in 0 ..< 1000 { - metadata.addBinary([1], forKey: "key") - } - - benchmark.startMeasurement() - for value in metadata[binaryValues: "key"] { - blackHole(value) - } - benchmark.stopMeasurement() - } - } - - Benchmark( - "Metadata_Iterate_binary_values_when_only_strings_stored", - configuration: config - ) { benchmark in - for _ in benchmark.scaledIterations { - var metadata = Metadata() - for i in 0 ..< 1000 { - metadata.addString("\(i)", forKey: "key") - } - - benchmark.startMeasurement() - for value in metadata[binaryValues: "key"] { - blackHole(value) - } - benchmark.stopMeasurement() - } - } -} diff --git a/Performance/Benchmarks/Package.swift b/Performance/Benchmarks/Package.swift deleted file mode 100644 index 2c1fe63ab..000000000 --- a/Performance/Benchmarks/Package.swift +++ /dev/null @@ -1,41 +0,0 @@ -// swift-tools-version: 5.8 -/* - * 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. - */ -import PackageDescription - -let package = Package( - name: "benchmarks", - platforms: [ - .macOS(.v13), - ], - dependencies: [ - .package(path: "../../"), - .package(url: "https://github.com/ordo-one/package-benchmark", from: "1.11.2") - ], - targets: [ - .executableTarget( - name: "GRPCSwiftBenchmark", - dependencies: [ - .product(name: "Benchmark", package: "package-benchmark"), - .product(name: "_GRPCCore", package: "grpc-swift") - ], - path: "Benchmarks/GRPCSwiftBenchmark", - plugins: [ - .plugin(name: "BenchmarkPlugin", package: "package-benchmark") - ] - ), - ] -) diff --git a/Performance/Benchmarks/Thresholds/6.0/GRPCSwiftBenchmark.Metadata_Add_binary.p90.json b/Performance/Benchmarks/Thresholds/6.0/GRPCSwiftBenchmark.Metadata_Add_binary.p90.json deleted file mode 100644 index b642696c1..000000000 --- a/Performance/Benchmarks/Thresholds/6.0/GRPCSwiftBenchmark.Metadata_Add_binary.p90.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "mallocCountTotal" : 11, - "memoryLeaked" : 0, - "releaseCount" : 3012, - "retainCount" : 2000, - "syscalls" : 0 -} diff --git a/Performance/Benchmarks/Thresholds/6.0/GRPCSwiftBenchmark.Metadata_Add_string.p90.json b/Performance/Benchmarks/Thresholds/6.0/GRPCSwiftBenchmark.Metadata_Add_string.p90.json deleted file mode 100644 index 7fde30a69..000000000 --- a/Performance/Benchmarks/Thresholds/6.0/GRPCSwiftBenchmark.Metadata_Add_string.p90.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "mallocCountTotal" : 11, - "memoryLeaked" : 0, - "releaseCount" : 4012, - "retainCount" : 2000, - "syscalls" : 0 -} diff --git a/Performance/Benchmarks/Thresholds/6.0/GRPCSwiftBenchmark.Metadata_Iterate_all_values.p90.json b/Performance/Benchmarks/Thresholds/6.0/GRPCSwiftBenchmark.Metadata_Iterate_all_values.p90.json deleted file mode 100644 index 1b1303873..000000000 --- a/Performance/Benchmarks/Thresholds/6.0/GRPCSwiftBenchmark.Metadata_Iterate_all_values.p90.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "mallocCountTotal" : 0, - "memoryLeaked" : 0, - "releaseCount" : 3001, - "retainCount" : 1000, - "syscalls" : 0 -} diff --git a/Performance/Benchmarks/Thresholds/6.0/GRPCSwiftBenchmark.Metadata_Iterate_binary_values_when_only_binary_values_stored.p90.json b/Performance/Benchmarks/Thresholds/6.0/GRPCSwiftBenchmark.Metadata_Iterate_binary_values_when_only_binary_values_stored.p90.json deleted file mode 100644 index 1b1303873..000000000 --- a/Performance/Benchmarks/Thresholds/6.0/GRPCSwiftBenchmark.Metadata_Iterate_binary_values_when_only_binary_values_stored.p90.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "mallocCountTotal" : 0, - "memoryLeaked" : 0, - "releaseCount" : 3001, - "retainCount" : 1000, - "syscalls" : 0 -} diff --git a/Performance/Benchmarks/Thresholds/6.0/GRPCSwiftBenchmark.Metadata_Iterate_binary_values_when_only_strings_stored.p90.json b/Performance/Benchmarks/Thresholds/6.0/GRPCSwiftBenchmark.Metadata_Iterate_binary_values_when_only_strings_stored.p90.json deleted file mode 100644 index b59f05063..000000000 --- a/Performance/Benchmarks/Thresholds/6.0/GRPCSwiftBenchmark.Metadata_Iterate_binary_values_when_only_strings_stored.p90.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "mallocCountTotal" : 2000, - "memoryLeaked" : 0, - "releaseCount" : 6001, - "retainCount" : 2000, - "syscalls" : 0 -} diff --git a/Performance/Benchmarks/Thresholds/6.0/GRPCSwiftBenchmark.Metadata_Iterate_string_values.p90.json b/Performance/Benchmarks/Thresholds/6.0/GRPCSwiftBenchmark.Metadata_Iterate_string_values.p90.json deleted file mode 100644 index 1b1303873..000000000 --- a/Performance/Benchmarks/Thresholds/6.0/GRPCSwiftBenchmark.Metadata_Iterate_string_values.p90.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "mallocCountTotal" : 0, - "memoryLeaked" : 0, - "releaseCount" : 3001, - "retainCount" : 1000, - "syscalls" : 0 -} diff --git a/Performance/Benchmarks/Thresholds/6.0/GRPCSwiftBenchmark.Metadata_Remove_values_for_key.p90.json b/Performance/Benchmarks/Thresholds/6.0/GRPCSwiftBenchmark.Metadata_Remove_values_for_key.p90.json deleted file mode 100644 index 5750750bc..000000000 --- a/Performance/Benchmarks/Thresholds/6.0/GRPCSwiftBenchmark.Metadata_Remove_values_for_key.p90.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "mallocCountTotal" : 0, - "memoryLeaked" : 0, - "releaseCount" : 2002001, - "retainCount" : 1999000, - "syscalls" : 0 -} diff --git a/Performance/Benchmarks/Thresholds/main/GRPCSwiftBenchmark.Metadata_Add_binary.p90.json b/Performance/Benchmarks/Thresholds/main/GRPCSwiftBenchmark.Metadata_Add_binary.p90.json deleted file mode 100644 index b642696c1..000000000 --- a/Performance/Benchmarks/Thresholds/main/GRPCSwiftBenchmark.Metadata_Add_binary.p90.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "mallocCountTotal" : 11, - "memoryLeaked" : 0, - "releaseCount" : 3012, - "retainCount" : 2000, - "syscalls" : 0 -} diff --git a/Performance/Benchmarks/Thresholds/main/GRPCSwiftBenchmark.Metadata_Add_string.p90.json b/Performance/Benchmarks/Thresholds/main/GRPCSwiftBenchmark.Metadata_Add_string.p90.json deleted file mode 100644 index 7fde30a69..000000000 --- a/Performance/Benchmarks/Thresholds/main/GRPCSwiftBenchmark.Metadata_Add_string.p90.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "mallocCountTotal" : 11, - "memoryLeaked" : 0, - "releaseCount" : 4012, - "retainCount" : 2000, - "syscalls" : 0 -} diff --git a/Performance/Benchmarks/Thresholds/main/GRPCSwiftBenchmark.Metadata_Iterate_all_values.p90.json b/Performance/Benchmarks/Thresholds/main/GRPCSwiftBenchmark.Metadata_Iterate_all_values.p90.json deleted file mode 100644 index 1b1303873..000000000 --- a/Performance/Benchmarks/Thresholds/main/GRPCSwiftBenchmark.Metadata_Iterate_all_values.p90.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "mallocCountTotal" : 0, - "memoryLeaked" : 0, - "releaseCount" : 3001, - "retainCount" : 1000, - "syscalls" : 0 -} diff --git a/Performance/Benchmarks/Thresholds/main/GRPCSwiftBenchmark.Metadata_Iterate_binary_values_when_only_binary_values_stored.p90.json b/Performance/Benchmarks/Thresholds/main/GRPCSwiftBenchmark.Metadata_Iterate_binary_values_when_only_binary_values_stored.p90.json deleted file mode 100644 index 1b1303873..000000000 --- a/Performance/Benchmarks/Thresholds/main/GRPCSwiftBenchmark.Metadata_Iterate_binary_values_when_only_binary_values_stored.p90.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "mallocCountTotal" : 0, - "memoryLeaked" : 0, - "releaseCount" : 3001, - "retainCount" : 1000, - "syscalls" : 0 -} diff --git a/Performance/Benchmarks/Thresholds/main/GRPCSwiftBenchmark.Metadata_Iterate_binary_values_when_only_strings_stored.p90.json b/Performance/Benchmarks/Thresholds/main/GRPCSwiftBenchmark.Metadata_Iterate_binary_values_when_only_strings_stored.p90.json deleted file mode 100644 index b4aba1c3f..000000000 --- a/Performance/Benchmarks/Thresholds/main/GRPCSwiftBenchmark.Metadata_Iterate_binary_values_when_only_strings_stored.p90.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "mallocCountTotal" : 2000, - "memoryLeaked" : 0, - "releaseCount" : 7001, - "retainCount" : 3000, - "syscalls" : 0 -} diff --git a/Performance/Benchmarks/Thresholds/main/GRPCSwiftBenchmark.Metadata_Iterate_string_values.p90.json b/Performance/Benchmarks/Thresholds/main/GRPCSwiftBenchmark.Metadata_Iterate_string_values.p90.json deleted file mode 100644 index 1b1303873..000000000 --- a/Performance/Benchmarks/Thresholds/main/GRPCSwiftBenchmark.Metadata_Iterate_string_values.p90.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "mallocCountTotal" : 0, - "memoryLeaked" : 0, - "releaseCount" : 3001, - "retainCount" : 1000, - "syscalls" : 0 -} diff --git a/Performance/Benchmarks/Thresholds/main/GRPCSwiftBenchmark.Metadata_Remove_values_for_key.p90.json b/Performance/Benchmarks/Thresholds/main/GRPCSwiftBenchmark.Metadata_Remove_values_for_key.p90.json deleted file mode 100644 index 5750750bc..000000000 --- a/Performance/Benchmarks/Thresholds/main/GRPCSwiftBenchmark.Metadata_Remove_values_for_key.p90.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "mallocCountTotal" : 0, - "memoryLeaked" : 0, - "releaseCount" : 2002001, - "retainCount" : 1999000, - "syscalls" : 0 -} 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/Protos/generate.sh b/Protos/generate.sh index 5eb6aa313..1103699e3 100755 --- a/Protos/generate.sh +++ b/Protos/generate.sh @@ -80,14 +80,6 @@ function generate_echo_v1_example { generate_grpc "$proto" "$(dirname "$proto")" "$output" "Visibility=Public" "TestClient=true" } -function generate_echo_v2_example { - local proto="$here/examples/echo/echo.proto" - local output="$root/Examples/v2/echo/Generated" - - generate_message "$proto" "$(dirname "$proto")" "$output" "Visibility=Internal" - generate_grpc "$proto" "$(dirname "$proto")" "$output" "Visibility=Internal" "_V2=true" -} - function generate_routeguide_v1_example { local proto="$here/examples/route_guide/route_guide.proto" local output="$root/Examples/v1/RouteGuide/Model" @@ -96,14 +88,6 @@ function generate_routeguide_v1_example { generate_grpc "$proto" "$(dirname "$proto")" "$output" "Visibility=Public" } -function generate_routeguide_v2_example { - local proto="$here/examples/route_guide/route_guide.proto" - local output="$root/Examples/v2/route-guide/Generated" - - generate_message "$proto" "$(dirname "$proto")" "$output" "Visibility=Internal" - generate_grpc "$proto" "$(dirname "$proto")" "$output" "Visibility=Internal" "_V2=true" -} - function generate_helloworld_v1_example { local proto="$here/upstream/grpc/examples/helloworld.proto" local output="$root/Examples/v1/HelloWorld/Model" @@ -112,14 +96,6 @@ function generate_helloworld_v1_example { generate_grpc "$proto" "$(dirname "$proto")" "$output" "Visibility=Public" } -function generate_helloworld_v2_example { - local proto="$here/upstream/grpc/examples/helloworld.proto" - local output="$root/Examples/v2/hello-world/Generated" - - generate_message "$proto" "$(dirname "$proto")" "$output" "Visibility=Internal" - generate_grpc "$proto" "$(dirname "$proto")" "$output" "Visibility=Internal" "_V2=true" -} - function generate_reflection_service { local proto_v1="$here/upstream/grpc/reflection/v1/reflection.proto" local output_v1="$root/Sources/GRPCReflectionService/v1" @@ -196,83 +172,12 @@ function generate_reflection_data_example { done } -function generate_rpc_code_for_tests { - local protos=( - "$here/upstream/grpc/service_config/service_config.proto" - "$here/upstream/grpc/lookup/v1/rls.proto" - "$here/upstream/grpc/lookup/v1/rls_config.proto" - "$here/upstream/google/rpc/code.proto" - ) - local output="$root/Tests/GRPCCoreTests/Configuration/Generated" - - for proto in "${protos[@]}"; do - generate_message "$proto" "$here/upstream" "$output" "Visibility=Internal" "FileNaming=DropPath" - done -} - -function generate_http2_transport_tests_service { - local proto="$here/tests/control/control.proto" - local output="$root/Tests/GRPCHTTP2TransportTests/Generated" - - generate_message "$proto" "$(dirname "$proto")" "$output" "Visibility=Internal" - generate_grpc "$proto" "$(dirname "$proto")" "$output" "Visibility=Internal" "Client=true" "Server=true" "_V2=true" -} - -function generate_service_messages_interop_tests { - local protos=( - "$here/tests/interoperability/src/proto/grpc/testing/empty_service.proto" - "$here/tests/interoperability/src/proto/grpc/testing/empty.proto" - "$here/tests/interoperability/src/proto/grpc/testing/messages.proto" - "$here/tests/interoperability/src/proto/grpc/testing/test.proto" - ) - local output="$root/Sources/InteroperabilityTests/Generated" - - for proto in "${protos[@]}"; do - generate_message "$proto" "$here/tests/interoperability" "$output" "Visibility=Public" "FileNaming=DropPath" "UseAccessLevelOnImports=true" - generate_grpc "$proto" "$here/tests/interoperability" "$output" "Visibility=Public" "Server=true" "_V2=true" "FileNaming=DropPath" "UseAccessLevelOnImports=true" - done -} - -function generate_worker_service { - local protos=( - "$here/upstream/grpc/testing/payloads.proto" - "$here/upstream/grpc/testing/control.proto" - "$here/upstream/grpc/testing/messages.proto" - "$here/upstream/grpc/testing/stats.proto" - "$here/upstream/grpc/testing/benchmark_service.proto" - "$here/upstream/grpc/testing/worker_service.proto" - ) - local output="$root/Sources/performance-worker/Generated" - - generate_message "$here/upstream/grpc/core/stats.proto" "$here/upstream" "$output" "Visibility=Internal" "FileNaming=PathToUnderscores" - - for proto in "${protos[@]}"; do - generate_message "$proto" "$here/upstream" "$output" "Visibility=Internal" "FileNaming=PathToUnderscores" - if [ "$proto" == "$here/upstream/grpc/testing/worker_service.proto" ]; then - generate_grpc "$proto" "$here/upstream" "$output" "Visibility=Internal" "Client=false" "_V2=true" "FileNaming=PathToUnderscores" - else - generate_grpc "$proto" "$here/upstream" "$output" "Visibility=Internal" "_V2=true" "FileNaming=PathToUnderscores" - fi - done -} - -function generate_health_service { - local proto="$here/upstream/grpc/health/v1/health.proto" - local output="$root/Sources/Services/Health/Generated" - - generate_message "$proto" "$(dirname "$proto")" "$output" "Visibility=Package" "UseAccessLevelOnImports=true" - generate_grpc "$proto" "$(dirname "$proto")" "$output" "Visibility=Package" "Client=true" "Server=true" "_V2=true" "UseAccessLevelOnImports=true" -} - #------------------------------------------------------------------------------ # Examples generate_echo_v1_example -generate_echo_v2_example generate_routeguide_v1_example -generate_routeguide_v2_example generate_helloworld_v1_example -generate_helloworld_v2_example generate_reflection_data_example # Reflection service and tests @@ -280,16 +185,5 @@ generate_reflection_service generate_reflection_client_for_tests generate_echo_reflection_data_for_tests -# Interoperability tests -generate_service_messages_interop_tests - # Misc. tests generate_normalization_for_tests -generate_rpc_code_for_tests -generate_http2_transport_tests_service - -# Performance worker service -generate_worker_service - -# Health -generate_health_service 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..b0e3b94bf 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,9 @@ 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 +176,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 diff --git a/Tests/GRPCCodeGenTests/Internal/Renderer/TextBasedRendererTests.swift b/Tests/GRPCCodeGenTests/Internal/Renderer/TextBasedRendererTests.swift deleted file mode 100644 index 65b5eb79b..000000000 --- a/Tests/GRPCCodeGenTests/Internal/Renderer/TextBasedRendererTests.swift +++ /dev/null @@ -1,1016 +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 XCTest - -@testable import GRPCCodeGen - -final class Test_TextBasedRenderer: XCTestCase { - - func testComment() throws { - try _test( - .inline( - #""" - Generated by foo - - Also, bar - """# - ), - renderedBy: { $0.renderComment(_:) }, - rendersAs: #""" - // Generated by foo - // - // Also, bar - """# - ) - try _test( - .doc( - #""" - Generated by foo - - Also, bar - """# - ), - renderedBy: { $0.renderComment(_:) }, - rendersAs: #""" - /// Generated by foo - /// - /// Also, bar - """# - ) - try _test( - .mark("Lorem ipsum", sectionBreak: false), - renderedBy: { $0.renderComment(_:) }, - rendersAs: #""" - // MARK: Lorem ipsum - """# - ) - try _test( - .mark("Lorem ipsum", sectionBreak: true), - renderedBy: { $0.renderComment(_:) }, - rendersAs: #""" - // MARK: - Lorem ipsum - """# - ) - try _test( - .inline( - """ - Generated by foo\r\nAlso, bar - """ - ), - renderedBy: { $0.renderComment(_:) }, - rendersAs: #""" - // Generated by foo - // Also, bar - """# - ) - try _test( - .preFormatted("/// Lorem ipsum\n"), - renderedBy: { $0.renderComment(_:) }, - rendersAs: """ - /// Lorem ipsum - """ - ) - try _test( - .preFormatted("/// Lorem ipsum\n\n/// Lorem ipsum\n"), - renderedBy: { $0.renderComment(_:) }, - rendersAs: """ - /// Lorem ipsum - - /// Lorem ipsum - """ - ) - } - - func testImports() throws { - try _test(nil, renderedBy: { $0.renderImports(_:) }, rendersAs: "") - try _test( - [ - ImportDescription(moduleName: "Foo"), - ImportDescription(moduleName: "Bar"), - ImportDescription(accessLevel: .fileprivate, moduleName: "BazFileprivate"), - ImportDescription(accessLevel: .private, moduleName: "BazPrivate"), - ImportDescription(accessLevel: .internal, moduleName: "BazInternal"), - ImportDescription(accessLevel: .package, moduleName: "BazPackage"), - ImportDescription(accessLevel: .public, moduleName: "BazPublic"), - ], - renderedBy: { $0.renderImports(_:) }, - rendersAs: #""" - import Foo - import Bar - fileprivate import BazFileprivate - private import BazPrivate - internal import BazInternal - package import BazPackage - public import BazPublic - """# - ) - try _test( - [ImportDescription(moduleName: "Foo", spi: "Secret")], - renderedBy: { $0.renderImports(_:) }, - rendersAs: #""" - @_spi(Secret) import Foo - """# - ) - try _test( - [ - ImportDescription( - moduleName: "Foo", - preconcurrency: .onOS(["Bar", "Baz"]) - ) - ], - renderedBy: { $0.renderImports(_:) }, - rendersAs: #""" - #if os(Bar) || os(Baz) - @preconcurrency import Foo - #else - import Foo - #endif - """# - ) - try _test( - [ - ImportDescription(moduleName: "Foo", preconcurrency: .always), - ImportDescription( - moduleName: "Bar", - spi: "Secret", - preconcurrency: .always - ), - ], - renderedBy: { $0.renderImports(_:) }, - rendersAs: #""" - @preconcurrency import Foo - @preconcurrency @_spi(Secret) import Bar - """# - ) - - try _test( - [ - ImportDescription( - moduleName: "Foo", - item: ImportDescription.Item(kind: .typealias, name: "Bar") - ), - ImportDescription( - moduleName: "Foo", - item: ImportDescription.Item(kind: .struct, name: "Baz") - ), - ImportDescription( - moduleName: "Foo", - item: ImportDescription.Item(kind: .class, name: "Bac") - ), - ImportDescription( - moduleName: "Foo", - item: ImportDescription.Item(kind: .enum, name: "Bap") - ), - ImportDescription( - moduleName: "Foo", - item: ImportDescription.Item(kind: .protocol, name: "Bat") - ), - ImportDescription( - moduleName: "Foo", - item: ImportDescription.Item(kind: .let, name: "Bam") - ), - ImportDescription( - moduleName: "Foo", - item: ImportDescription.Item(kind: .var, name: "Bag") - ), - ImportDescription( - moduleName: "Foo", - item: ImportDescription.Item(kind: .func, name: "Bak") - ), - ImportDescription( - moduleName: "Foo", - spi: "Secret", - item: ImportDescription.Item(kind: .func, name: "SecretBar") - ), - ImportDescription( - moduleName: "Foo", - preconcurrency: .always, - item: ImportDescription.Item(kind: .func, name: "PreconcurrencyBar") - ), - ], - renderedBy: { $0.renderImports(_:) }, - rendersAs: #""" - import typealias Foo.Bar - import struct Foo.Baz - import class Foo.Bac - import enum Foo.Bap - import protocol Foo.Bat - import let Foo.Bam - import var Foo.Bag - import func Foo.Bak - @_spi(Secret) import func Foo.SecretBar - @preconcurrency import func Foo.PreconcurrencyBar - """# - ) - } - - func testAccessModifiers() throws { - try _test( - .public, - renderedBy: { $0.renderedAccessModifier(_:) }, - rendersAs: #""" - public - """# - ) - try _test( - .internal, - renderedBy: { $0.renderedAccessModifier(_:) }, - rendersAs: #""" - internal - """# - ) - try _test( - .fileprivate, - renderedBy: { $0.renderedAccessModifier(_:) }, - rendersAs: #""" - fileprivate - """# - ) - try _test( - .private, - renderedBy: { $0.renderedAccessModifier(_:) }, - rendersAs: #""" - private - """# - ) - } - - func testLiterals() throws { - try _test( - .string("hi"), - renderedBy: { $0.renderLiteral(_:) }, - rendersAs: #""" - "hi" - """# - ) - try _test( - .string("this string: \"foo\""), - renderedBy: { $0.renderLiteral(_:) }, - rendersAs: #""" - #"this string: "foo""# - """# - ) - try _test( - .nil, - renderedBy: { $0.renderLiteral(_:) }, - rendersAs: #""" - nil - """# - ) - try _test( - .array([]), - renderedBy: { $0.renderLiteral(_:) }, - rendersAs: #""" - [] - """# - ) - try _test( - .array([.literal(.nil)]), - renderedBy: { $0.renderLiteral(_:) }, - rendersAs: #""" - [ - nil - ] - """# - ) - try _test( - .array([.literal(.nil), .literal(.nil)]), - renderedBy: { $0.renderLiteral(_:) }, - rendersAs: #""" - [ - nil, - nil - ] - """# - ) - try _test( - .dictionary([]), - renderedBy: { $0.renderLiteral(_:) }, - rendersAs: #""" - [:] - """# - ) - try _test( - .dictionary([.init(key: .literal("foo"), value: .literal("bar"))]), - renderedBy: { $0.renderLiteral(_:) }, - rendersAs: #""" - [ - "foo": "bar" - ] - """# - ) - try _test( - .dictionary([ - .init(key: .literal("foo"), value: .literal("bar")), - .init(key: .literal("bar"), value: .literal("baz")), - ]), - renderedBy: { $0.renderLiteral(_:) }, - rendersAs: #""" - [ - "foo": "bar", - "bar": "baz" - ] - """# - ) - } - - func testExpression() throws { - try _test( - .literal(.nil), - renderedBy: { $0.renderExpression(_:) }, - rendersAs: #""" - nil - """# - ) - try _test( - .identifierPattern("foo"), - renderedBy: { $0.renderExpression(_:) }, - rendersAs: #""" - foo - """# - ) - try _test( - .memberAccess(.init(left: .identifierPattern("foo"), right: "bar")), - renderedBy: { $0.renderExpression(_:) }, - rendersAs: #""" - foo.bar - """# - ) - try _test( - .functionCall( - .init( - calledExpression: .identifierPattern("callee"), - arguments: [.init(label: nil, expression: .identifierPattern("foo"))] - ) - ), - renderedBy: { $0.renderExpression(_:) }, - rendersAs: #""" - callee(foo) - """# - ) - } - - func testDeclaration() throws { - try _test( - .variable(kind: .let, left: "foo"), - renderedBy: { $0.renderDeclaration(_:) }, - rendersAs: #""" - let foo - """# - ) - try _test( - .extension(.init(onType: "String", declarations: [])), - renderedBy: { $0.renderDeclaration(_:) }, - rendersAs: #""" - extension String { - } - """# - ) - try _test( - .struct(.init(name: "Foo")), - renderedBy: { $0.renderDeclaration(_:) }, - rendersAs: #""" - struct Foo {} - """# - ) - try _test( - .protocol(.init(name: "Foo")), - renderedBy: { $0.renderDeclaration(_:) }, - rendersAs: #""" - protocol Foo {} - """# - ) - try _test( - .enum(.init(name: "Foo")), - renderedBy: { $0.renderDeclaration(_:) }, - rendersAs: #""" - enum Foo {} - """# - ) - try _test( - .typealias(.init(name: "foo", existingType: .member(["Foo", "Bar"]))), - renderedBy: { $0.renderDeclaration(_:) }, - rendersAs: #""" - typealias foo = Foo.Bar - """# - ) - try _test( - .function(FunctionDescription.init(kind: .function(name: "foo"), body: [])), - renderedBy: { $0.renderDeclaration(_:) }, - rendersAs: #""" - func foo() {} - """# - ) - } - - func testFunctionKind() throws { - try _test( - .initializer, - renderedBy: { $0.renderedFunctionKind(_:) }, - rendersAs: #""" - init - """# - ) - try _test( - .function(name: "funky"), - renderedBy: { $0.renderedFunctionKind(_:) }, - rendersAs: #""" - func funky - """# - ) - try _test( - .function(name: "funky", isStatic: true), - renderedBy: { $0.renderedFunctionKind(_:) }, - rendersAs: #""" - static func funky - """# - ) - } - - func testFunctionKeyword() throws { - try _test( - .throws, - renderedBy: { $0.renderedFunctionKeyword(_:) }, - rendersAs: #""" - throws - """# - ) - try _test( - .async, - renderedBy: { $0.renderedFunctionKeyword(_:) }, - rendersAs: #""" - async - """# - ) - } - - func testParameter() throws { - try _test( - .init(label: "l", name: "n", type: .member("T"), defaultValue: .literal(.nil)), - renderedBy: { $0.renderParameter(_:) }, - rendersAs: #""" - l n: T = nil - """# - ) - try _test( - .init(label: nil, name: "n", type: .member("T"), defaultValue: .literal(.nil)), - renderedBy: { $0.renderParameter(_:) }, - rendersAs: #""" - _ n: T = nil - """# - ) - try _test( - .init(label: "l", name: nil, type: .member("T"), defaultValue: .literal(.nil)), - renderedBy: { $0.renderParameter(_:) }, - rendersAs: #""" - l: T = nil - """# - ) - try _test( - .init(label: nil, name: nil, type: .member("T"), defaultValue: .literal(.nil)), - renderedBy: { $0.renderParameter(_:) }, - rendersAs: #""" - _: T = nil - """# - ) - try _test( - .init(label: nil, name: nil, type: .member("T"), defaultValue: nil), - renderedBy: { $0.renderParameter(_:) }, - rendersAs: #""" - _: T - """# - ) - } - - func testGenericFunction() throws { - try _test( - .init( - accessModifier: .public, - kind: .function(name: "f"), - generics: [.member("R")], - parameters: [], - whereClause: WhereClause(requirements: [.conformance("R", "Sendable")]), - body: [] - ), - renderedBy: { $0.renderFunction(_:) }, - rendersAs: #""" - public func f() where R: Sendable {} - """# - ) - try _test( - .init( - accessModifier: .public, - kind: .function(name: "f"), - generics: [.member("R"), .member("T")], - parameters: [], - whereClause: WhereClause(requirements: [ - .conformance("R", "Sendable"), .conformance("T", "Encodable"), - ]), - body: [] - ), - renderedBy: { $0.renderFunction(_:) }, - rendersAs: #""" - public func f() where R: Sendable, T: Encodable {} - """# - ) - } - - func testFunction() throws { - try _test( - .init(accessModifier: .public, kind: .function(name: "f"), parameters: [], body: []), - renderedBy: { $0.renderFunction(_:) }, - rendersAs: #""" - public func f() {} - """# - ) - try _test( - .init( - accessModifier: .public, - kind: .function(name: "f"), - parameters: [.init(label: "a", name: "b", type: .member("C"), defaultValue: nil)], - body: [] - ), - renderedBy: { $0.renderFunction(_:) }, - rendersAs: #""" - public func f(a b: C) {} - """# - ) - try _test( - .init( - accessModifier: .public, - kind: .function(name: "f"), - parameters: [ - .init(label: "a", name: "b", type: .member("C"), defaultValue: nil), - .init(label: nil, name: "d", type: .member("E"), defaultValue: .literal(.string("f"))), - ], - body: [] - ), - renderedBy: { $0.renderFunction(_:) }, - rendersAs: #""" - public func f( - a b: C, - _ d: E = "f" - ) {} - """# - ) - try _test( - .init( - kind: .function(name: "f"), - parameters: [], - keywords: [.async, .throws], - returnType: .identifierType(TypeName.string) - ), - renderedBy: { $0.renderFunction(_:) }, - rendersAs: #""" - func f() async throws -> Swift.String - """# - ) - } - - func testIdentifiers() throws { - try _test( - .pattern("foo"), - renderedBy: { $0.renderIdentifier(_:) }, - rendersAs: #""" - foo - """# - ) - } - - func testMemberAccess() throws { - try _test( - .init(left: .identifierPattern("foo"), right: "bar"), - renderedBy: { $0.renderMemberAccess(_:) }, - rendersAs: #""" - foo.bar - """# - ) - try _test( - .init(left: nil, right: "bar"), - renderedBy: { $0.renderMemberAccess(_:) }, - rendersAs: #""" - .bar - """# - ) - } - - func testFunctionCallArgument() throws { - try _test( - .init(label: "foo", expression: .identifierPattern("bar")), - renderedBy: { $0.renderFunctionCallArgument(_:) }, - rendersAs: #""" - foo: bar - """# - ) - try _test( - .init(label: nil, expression: .identifierPattern("bar")), - renderedBy: { $0.renderFunctionCallArgument(_:) }, - rendersAs: #""" - bar - """# - ) - } - - func testFunctionCall() throws { - try _test( - .functionCall(.init(calledExpression: .identifierPattern("callee"))), - renderedBy: { $0.renderExpression(_:) }, - rendersAs: #""" - callee() - """# - ) - try _test( - .functionCall( - .init( - calledExpression: .identifierPattern("callee"), - arguments: [.init(label: "foo", expression: .identifierPattern("bar"))] - ) - ), - renderedBy: { $0.renderExpression(_:) }, - rendersAs: #""" - callee(foo: bar) - """# - ) - try _test( - .functionCall( - .init( - calledExpression: .identifierPattern("callee"), - arguments: [ - .init(label: "foo", expression: .identifierPattern("bar")), - .init(label: "baz", expression: .identifierPattern("boo")), - ] - ) - ), - renderedBy: { $0.renderExpression(_:) }, - rendersAs: #""" - callee( - foo: bar, - baz: boo - ) - """# - ) - } - - func testExtension() throws { - try _test( - .init( - accessModifier: .public, - onType: "Info", - declarations: [.variable(kind: .let, left: "foo", type: .member("Int"))] - ), - renderedBy: { $0.renderExtension(_:) }, - rendersAs: #""" - public extension Info { - let foo: Int - } - """# - ) - } - - func testDeprecation() throws { - try _test( - .init(), - renderedBy: { $0.renderDeprecation(_:) }, - rendersAs: #""" - @available(*, deprecated) - """# - ) - try _test( - .init(message: "some message"), - renderedBy: { $0.renderDeprecation(_:) }, - rendersAs: #""" - @available(*, deprecated, message: "some message") - """# - ) - try _test( - .init(renamed: "newSymbol(param:)"), - renderedBy: { $0.renderDeprecation(_:) }, - rendersAs: #""" - @available(*, deprecated, renamed: "newSymbol(param:)") - """# - ) - try _test( - .init(message: "some message", renamed: "newSymbol(param:)"), - renderedBy: { $0.renderDeprecation(_:) }, - rendersAs: #""" - @available(*, deprecated, message: "some message", renamed: "newSymbol(param:)") - """# - ) - } - - func testAvailability() throws { - try _test( - .init(osVersions: [ - .init(os: .macOS, version: "12.0"), - .init(os: .iOS, version: "13.1.2"), - .init(os: .watchOS, version: "8.1.2"), - .init(os: .tvOS, version: "15.0.2"), - ]), - renderedBy: { $0.renderAvailability(_:) }, - rendersAs: #""" - @available(macOS 12.0, iOS 13.1.2, watchOS 8.1.2, tvOS 15.0.2, *) - """# - ) - } - - func testBindingKind() throws { - try _test( - .var, - renderedBy: { $0.renderedBindingKind(_:) }, - rendersAs: #""" - var - """# - ) - try _test( - .let, - renderedBy: { $0.renderedBindingKind(_:) }, - rendersAs: #""" - let - """# - ) - } - - func testVariable() throws { - try _test( - .init( - accessModifier: .public, - isStatic: true, - kind: .let, - left: .identifierPattern("foo"), - type: .init(TypeName.string), - right: .literal(.string("bar")) - ), - renderedBy: { $0.renderVariable(_:) }, - rendersAs: #""" - public static let foo: Swift.String = "bar" - """# - ) - try _test( - .init( - accessModifier: .internal, - isStatic: false, - kind: .var, - left: .identifierPattern("foo"), - type: nil, - right: nil - ), - renderedBy: { $0.renderVariable(_:) }, - rendersAs: #""" - internal var foo - """# - ) - try _test( - .init( - kind: .var, - left: .identifierPattern("foo"), - type: .init(TypeName.int), - getter: [CodeBlock.expression(.literal(.int(42)))] - ), - renderedBy: { $0.renderVariable(_:) }, - rendersAs: #""" - var foo: Swift.Int { - 42 - } - """# - ) - try _test( - .init( - kind: .var, - left: .identifierPattern("foo"), - type: .init(TypeName.int), - getter: [CodeBlock.expression(.literal(.int(42)))], - getterEffects: [.throws] - ), - renderedBy: { $0.renderVariable(_:) }, - rendersAs: #""" - var foo: Swift.Int { - get throws { - 42 - } - } - """# - ) - } - - func testStruct() throws { - try _test( - .init(name: "Structy"), - renderedBy: { $0.renderStruct(_:) }, - rendersAs: #""" - struct Structy {} - """# - ) - } - - func testProtocol() throws { - try _test( - .init(name: "Protocoly"), - renderedBy: { $0.renderProtocol(_:) }, - rendersAs: #""" - protocol Protocoly {} - """# - ) - } - - func testEnum() throws { - try _test( - .init(name: "Enumy"), - renderedBy: { $0.renderEnum(_:) }, - rendersAs: #""" - enum Enumy {} - """# - ) - } - - func testCodeBlockItem() throws { - try _test( - .declaration(.variable(kind: .let, left: "foo")), - renderedBy: { $0.renderCodeBlockItem(_:) }, - rendersAs: #""" - let foo - """# - ) - try _test( - .expression(.literal(.nil)), - renderedBy: { $0.renderCodeBlockItem(_:) }, - rendersAs: #""" - nil - """# - ) - } - - func testCodeBlock() throws { - try _test( - .init( - comment: .inline("- MARK: Section"), - item: .declaration(.variable(kind: .let, left: "foo")) - ), - renderedBy: { $0.renderCodeBlock(_:) }, - rendersAs: #""" - // - MARK: Section - let foo - """# - ) - try _test( - .init(comment: nil, item: .declaration(.variable(kind: .let, left: "foo"))), - renderedBy: { $0.renderCodeBlock(_:) }, - rendersAs: #""" - let foo - """# - ) - } - - func testTypealias() throws { - try _test( - .init(name: "inty", existingType: .member("Int")), - renderedBy: { $0.renderTypealias(_:) }, - rendersAs: #""" - typealias inty = Int - """# - ) - try _test( - .init(accessModifier: .private, name: "inty", existingType: .member("Int")), - renderedBy: { $0.renderTypealias(_:) }, - rendersAs: #""" - private typealias inty = Int - """# - ) - } - - func testFile() throws { - try _test( - .init( - topComment: .inline("hi"), - imports: [.init(moduleName: "Foo")], - codeBlocks: [.init(comment: nil, item: .declaration(.struct(.init(name: "Bar"))))] - ), - renderedBy: { $0.renderFile(_:) }, - rendersAs: #""" - // hi - - import Foo - - struct Bar {} - """# - ) - } - - func testIndentation() throws { - try _test( - .init( - topComment: .inline("hi"), - imports: [.init(moduleName: "Foo")], - codeBlocks: [ - .init( - comment: nil, - item: .declaration(.struct(.init(name: "Bar", members: [.struct(.init(name: "Baz"))]))) - ) - ] - ), - renderedBy: { $0.renderFile(_:) }, - rendersAs: #""" - // hi - - import Foo - - struct Bar { - struct Baz {} - } - """#, - indentation: 2 - ) - - try _test( - .array([.literal(.nil), .literal(.nil)]), - renderedBy: { $0.renderLiteral(_:) }, - rendersAs: #""" - [ - nil, - nil - ] - """#, - indentation: 3 - ) - - try _test( - .init( - kind: .var, - left: .identifierPattern("foo"), - type: .init(TypeName.int), - getter: [CodeBlock.expression(.literal(.int(42)))], - getterEffects: [.throws] - ), - renderedBy: { $0.renderVariable(_:) }, - rendersAs: #""" - var foo: Swift.Int { - get throws { - 42 - } - } - """#, - indentation: 5 - ) - } -} - -extension Test_TextBasedRenderer { - func _test( - _ input: Input, - renderedBy renderClosure: (TextBasedRenderer) -> ((Input) -> String), - rendersAs output: String, - file: StaticString = #filePath, - line: UInt = #line, - indentation: Int = 4 - ) throws { - let renderer = TextBasedRenderer(indentation: indentation) - XCTAssertEqual(renderClosure(renderer)(input), output, file: file, line: line) - } - - func _test( - _ input: Input, - renderedBy renderClosure: (TextBasedRenderer) -> ((Input) -> Void), - rendersAs output: String, - file: StaticString = #filePath, - line: UInt = #line, - indentation: Int = 4 - ) throws { - try _test( - input, - renderedBy: { renderer in - let closure = renderClosure(renderer) - return { input in - closure(input) - return renderer.renderedContents() - } - }, - rendersAs: output, - indentation: indentation - ) - } -} diff --git a/Tests/GRPCCodeGenTests/Internal/Translator/ClientCodeTranslatorSnippetBasedTests.swift b/Tests/GRPCCodeGenTests/Internal/Translator/ClientCodeTranslatorSnippetBasedTests.swift deleted file mode 100644 index 88a713679..000000000 --- a/Tests/GRPCCodeGenTests/Internal/Translator/ClientCodeTranslatorSnippetBasedTests.swift +++ /dev/null @@ -1,813 +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 os(macOS) || os(Linux) // swift-format doesn't like canImport(Foundation.Process) - -import XCTest - -@testable import GRPCCodeGen - -final class ClientCodeTranslatorSnippetBasedTests: XCTestCase { - typealias MethodDescriptor = GRPCCodeGen.CodeGenerationRequest.ServiceDescriptor.MethodDescriptor - typealias ServiceDescriptor = GRPCCodeGen.CodeGenerationRequest.ServiceDescriptor - typealias Name = GRPCCodeGen.CodeGenerationRequest.Name - - func testClientCodeTranslatorUnaryMethod() throws { - let method = MethodDescriptor( - documentation: "/// Documentation for MethodA", - name: Name(base: "MethodA", generatedUpperCase: "MethodA", generatedLowerCase: "methodA"), - isInputStreaming: false, - isOutputStreaming: false, - inputType: "NamespaceA_ServiceARequest", - outputType: "NamespaceA_ServiceAResponse" - ) - let service = ServiceDescriptor( - documentation: "/// Documentation for ServiceA", - name: Name(base: "ServiceA", generatedUpperCase: "ServiceA", generatedLowerCase: ""), - namespace: Name(base: "namespaceA", generatedUpperCase: "NamespaceA", generatedLowerCase: ""), - methods: [method] - ) - let expectedSwift = - """ - /// Documentation for ServiceA - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public protocol NamespaceA_ServiceAClientProtocol: Sendable { - /// Documentation for MethodA - func methodA( - 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 NamespaceA_ServiceA.ClientProtocol { - public func methodA( - 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.methodA( - 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 NamespaceA_ServiceA.ClientProtocol { - /// Documentation for MethodA - public func methodA( - _ message: NamespaceA_ServiceARequest, - 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.methodA( - request: request, - options: options, - handleResponse - ) - } - } - /// Documentation for ServiceA - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public struct NamespaceA_ServiceAClient: NamespaceA_ServiceA.ClientProtocol { - private let client: GRPCCore.GRPCClient - - public init(wrapping client: GRPCCore.GRPCClient) { - self.client = client - } - - /// Documentation for MethodA - public func methodA( - 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: NamespaceA_ServiceA.Method.MethodA.descriptor, - serializer: serializer, - deserializer: deserializer, - options: options, - handler: body - ) - } - } - """ - - try self.assertClientCodeTranslation( - codeGenerationRequest: makeCodeGenerationRequest(services: [service]), - expectedSwift: expectedSwift, - accessLevel: .public - ) - } - - func testClientCodeTranslatorClientStreamingMethod() throws { - let method = MethodDescriptor( - documentation: "/// Documentation for MethodA", - name: Name(base: "MethodA", generatedUpperCase: "MethodA", generatedLowerCase: "methodA"), - isInputStreaming: true, - isOutputStreaming: false, - inputType: "NamespaceA_ServiceARequest", - outputType: "NamespaceA_ServiceAResponse" - ) - let service = ServiceDescriptor( - documentation: "/// Documentation for ServiceA", - name: Name(base: "ServiceA", generatedUpperCase: "ServiceA", generatedLowerCase: ""), - namespace: Name(base: "namespaceA", generatedUpperCase: "NamespaceA", generatedLowerCase: ""), - methods: [method] - ) - let expectedSwift = - """ - /// Documentation for ServiceA - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public protocol NamespaceA_ServiceAClientProtocol: Sendable { - /// Documentation for MethodA - func methodA( - 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 - } - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - extension NamespaceA_ServiceA.ClientProtocol { - public func methodA( - 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.methodA( - 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 NamespaceA_ServiceA.ClientProtocol { - /// Documentation for MethodA - public func methodA( - 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.methodA( - request: request, - options: options, - handleResponse - ) - } - } - /// Documentation for ServiceA - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public struct NamespaceA_ServiceAClient: NamespaceA_ServiceA.ClientProtocol { - private let client: GRPCCore.GRPCClient - - public init(wrapping client: GRPCCore.GRPCClient) { - self.client = client - } - - /// Documentation for MethodA - 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.clientStreaming( - request: request, - descriptor: NamespaceA_ServiceA.Method.MethodA.descriptor, - serializer: serializer, - deserializer: deserializer, - options: options, - handler: body - ) - } - } - """ - - try self.assertClientCodeTranslation( - codeGenerationRequest: makeCodeGenerationRequest(services: [service]), - expectedSwift: expectedSwift, - accessLevel: .public - ) - } - - func testClientCodeTranslatorServerStreamingMethod() throws { - let method = MethodDescriptor( - documentation: "/// Documentation for MethodA", - name: Name(base: "MethodA", generatedUpperCase: "MethodA", generatedLowerCase: "methodA"), - isInputStreaming: false, - isOutputStreaming: true, - inputType: "NamespaceA_ServiceARequest", - outputType: "NamespaceA_ServiceAResponse" - ) - let service = ServiceDescriptor( - documentation: "/// Documentation for ServiceA", - name: Name(base: "ServiceA", generatedUpperCase: "ServiceA", generatedLowerCase: ""), - namespace: Name(base: "namespaceA", generatedUpperCase: "NamespaceA", generatedLowerCase: ""), - methods: [method] - ) - let expectedSwift = - """ - /// Documentation for ServiceA - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public protocol NamespaceA_ServiceAClientProtocol: Sendable { - /// Documentation for MethodA - func methodA( - 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 NamespaceA_ServiceA.ClientProtocol { - public func methodA( - 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.methodA( - 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 NamespaceA_ServiceA.ClientProtocol { - /// Documentation for MethodA - public func methodA( - _ message: NamespaceA_ServiceARequest, - 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.methodA( - request: request, - options: options, - handleResponse - ) - } - } - /// Documentation for ServiceA - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public struct NamespaceA_ServiceAClient: NamespaceA_ServiceA.ClientProtocol { - private let client: GRPCCore.GRPCClient - - public init(wrapping client: GRPCCore.GRPCClient) { - self.client = client - } - - /// Documentation for MethodA - public func methodA( - 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: NamespaceA_ServiceA.Method.MethodA.descriptor, - serializer: serializer, - deserializer: deserializer, - options: options, - handler: body - ) - } - } - """ - - try self.assertClientCodeTranslation( - codeGenerationRequest: makeCodeGenerationRequest(services: [service]), - expectedSwift: expectedSwift, - accessLevel: .public - ) - } - - func testClientCodeTranslatorBidirectionalStreamingMethod() throws { - let method = MethodDescriptor( - documentation: "/// Documentation for MethodA", - name: Name(base: "MethodA", generatedUpperCase: "MethodA", generatedLowerCase: "methodA"), - isInputStreaming: true, - isOutputStreaming: true, - inputType: "NamespaceA_ServiceARequest", - outputType: "NamespaceA_ServiceAResponse" - ) - let service = ServiceDescriptor( - documentation: "/// Documentation for ServiceA", - name: Name(base: "ServiceA", generatedUpperCase: "ServiceA", generatedLowerCase: ""), - namespace: Name(base: "namespaceA", generatedUpperCase: "NamespaceA", generatedLowerCase: ""), - methods: [method] - ) - let expectedSwift = - """ - /// Documentation for ServiceA - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public protocol NamespaceA_ServiceAClientProtocol: Sendable { - /// Documentation for MethodA - func methodA( - 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 NamespaceA_ServiceA.ClientProtocol { - public func methodA( - 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.methodA( - 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 NamespaceA_ServiceA.ClientProtocol { - /// Documentation for MethodA - public func methodA( - 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.methodA( - request: request, - options: options, - handleResponse - ) - } - } - /// Documentation for ServiceA - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public struct NamespaceA_ServiceAClient: NamespaceA_ServiceA.ClientProtocol { - private let client: GRPCCore.GRPCClient - - public init(wrapping client: GRPCCore.GRPCClient) { - self.client = client - } - - /// Documentation for MethodA - public func methodA( - 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: NamespaceA_ServiceA.Method.MethodA.descriptor, - serializer: serializer, - deserializer: deserializer, - options: options, - handler: body - ) - } - } - """ - - try self.assertClientCodeTranslation( - codeGenerationRequest: makeCodeGenerationRequest(services: [service]), - expectedSwift: expectedSwift, - accessLevel: .public - ) - } - - func testClientCodeTranslatorMultipleMethod() throws { - let methodA = MethodDescriptor( - documentation: "/// Documentation for MethodA", - name: Name(base: "MethodA", generatedUpperCase: "MethodA", generatedLowerCase: "methodA"), - isInputStreaming: true, - isOutputStreaming: false, - inputType: "NamespaceA_ServiceARequest", - outputType: "NamespaceA_ServiceAResponse" - ) - let methodB = MethodDescriptor( - documentation: "/// Documentation for MethodB", - name: Name(base: "MethodB", generatedUpperCase: "MethodB", generatedLowerCase: "methodB"), - isInputStreaming: false, - isOutputStreaming: true, - inputType: "NamespaceA_ServiceARequest", - outputType: "NamespaceA_ServiceAResponse" - ) - let service = ServiceDescriptor( - documentation: "/// Documentation for ServiceA", - name: Name(base: "ServiceA", generatedUpperCase: "ServiceA", generatedLowerCase: ""), - namespace: Name(base: "namespaceA", generatedUpperCase: "NamespaceA", generatedLowerCase: ""), - methods: [methodA, methodB] - ) - let expectedSwift = - """ - /// Documentation for ServiceA - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - package protocol NamespaceA_ServiceAClientProtocol: Sendable { - /// Documentation for MethodA - func methodA( - 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 - - /// Documentation for MethodB - func methodB( - 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 NamespaceA_ServiceA.ClientProtocol { - package func methodA( - 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.methodA( - request: request, - serializer: GRPCProtobuf.ProtobufSerializer(), - deserializer: GRPCProtobuf.ProtobufDeserializer(), - options: options, - body - ) - } - - package func methodB( - 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.methodB( - 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 NamespaceA_ServiceA.ClientProtocol { - /// Documentation for MethodA - package func methodA( - 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.methodA( - request: request, - options: options, - handleResponse - ) - } - - /// Documentation for MethodB - package func methodB( - _ message: NamespaceA_ServiceARequest, - 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.methodB( - request: request, - options: options, - handleResponse - ) - } - } - /// Documentation for ServiceA - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - package struct NamespaceA_ServiceAClient: NamespaceA_ServiceA.ClientProtocol { - private let client: GRPCCore.GRPCClient - - package init(wrapping client: GRPCCore.GRPCClient) { - self.client = client - } - - /// Documentation for MethodA - package 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.clientStreaming( - request: request, - descriptor: NamespaceA_ServiceA.Method.MethodA.descriptor, - serializer: serializer, - deserializer: deserializer, - options: options, - handler: body - ) - } - - /// Documentation for MethodB - package func methodB( - 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: NamespaceA_ServiceA.Method.MethodB.descriptor, - serializer: serializer, - deserializer: deserializer, - options: options, - handler: body - ) - } - } - """ - - try self.assertClientCodeTranslation( - codeGenerationRequest: makeCodeGenerationRequest(services: [service]), - expectedSwift: expectedSwift, - accessLevel: .package - ) - } - - func testClientCodeTranslatorNoNamespaceService() throws { - let method = MethodDescriptor( - documentation: "/// Documentation for MethodA", - name: Name(base: "MethodA", generatedUpperCase: "MethodA", generatedLowerCase: "methodA"), - isInputStreaming: false, - isOutputStreaming: false, - inputType: "ServiceARequest", - outputType: "ServiceAResponse" - ) - let service = ServiceDescriptor( - documentation: "/// Documentation for ServiceA", - name: Name(base: "ServiceA", generatedUpperCase: "ServiceA", generatedLowerCase: ""), - namespace: Name(base: "", generatedUpperCase: "", generatedLowerCase: ""), - methods: [method] - ) - let expectedSwift = - """ - /// Documentation for ServiceA - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - internal protocol ServiceAClientProtocol: Sendable { - /// Documentation for MethodA - func methodA( - 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 ServiceA.ClientProtocol { - internal func methodA( - 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.methodA( - 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 ServiceA.ClientProtocol { - /// Documentation for MethodA - internal func methodA( - _ message: ServiceARequest, - 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.methodA( - request: request, - options: options, - handleResponse - ) - } - } - /// Documentation for ServiceA - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - internal struct ServiceAClient: ServiceA.ClientProtocol { - private let client: GRPCCore.GRPCClient - - internal init(wrapping client: GRPCCore.GRPCClient) { - self.client = client - } - - /// Documentation for MethodA - internal func methodA( - 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: ServiceA.Method.MethodA.descriptor, - serializer: serializer, - deserializer: deserializer, - options: options, - handler: body - ) - } - } - """ - - try self.assertClientCodeTranslation( - codeGenerationRequest: makeCodeGenerationRequest(services: [service]), - expectedSwift: expectedSwift, - accessLevel: .internal - ) - } - - func testClientCodeTranslatorMultipleServices() throws { - let serviceA = ServiceDescriptor( - documentation: "/// Documentation for ServiceA", - name: Name(base: "ServiceA", generatedUpperCase: "ServiceA", generatedLowerCase: ""), - namespace: Name( - base: "nammespaceA", - generatedUpperCase: "NamespaceA", - generatedLowerCase: "" - ), - methods: [] - ) - let serviceB = ServiceDescriptor( - documentation: """ - /// Documentation for ServiceB - /// - /// Line 2 - """, - name: Name(base: "ServiceB", generatedUpperCase: "ServiceB", generatedLowerCase: ""), - namespace: Name(base: "", generatedUpperCase: "", generatedLowerCase: ""), - methods: [] - ) - let expectedSwift = - """ - /// Documentation for ServiceA - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public protocol NamespaceA_ServiceAClientProtocol: Sendable {} - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - extension NamespaceA_ServiceA.ClientProtocol { - } - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - extension NamespaceA_ServiceA.ClientProtocol { - } - /// Documentation for ServiceA - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public struct NamespaceA_ServiceAClient: NamespaceA_ServiceA.ClientProtocol { - private let client: GRPCCore.GRPCClient - - public init(wrapping client: GRPCCore.GRPCClient) { - self.client = client - } - } - /// Documentation for ServiceB - /// - /// Line 2 - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public protocol ServiceBClientProtocol: Sendable {} - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - extension ServiceB.ClientProtocol { - } - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - extension ServiceB.ClientProtocol { - } - /// Documentation for ServiceB - /// - /// Line 2 - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public struct ServiceBClient: ServiceB.ClientProtocol { - private let client: GRPCCore.GRPCClient - - public init(wrapping client: GRPCCore.GRPCClient) { - self.client = client - } - } - """ - - try self.assertClientCodeTranslation( - codeGenerationRequest: makeCodeGenerationRequest(services: [serviceA, serviceB]), - expectedSwift: expectedSwift, - accessLevel: .public - ) - } - - private func assertClientCodeTranslation( - codeGenerationRequest: CodeGenerationRequest, - expectedSwift: String, - accessLevel: SourceGenerator.Config.AccessLevel, - file: StaticString = #filePath, - line: UInt = #line - ) throws { - let translator = ClientCodeTranslator(accessLevel: accessLevel) - let codeBlocks = try translator.translate(from: codeGenerationRequest) - let renderer = TextBasedRenderer.default - renderer.renderCodeBlocks(codeBlocks) - let contents = renderer.renderedContents() - try XCTAssertEqualWithDiff(contents, expectedSwift, file: file, line: line) - } -} - -#endif // os(macOS) || os(Linux) diff --git a/Tests/GRPCCodeGenTests/Internal/Translator/IDLToStructuredSwiftTranslatorSnippetBasedTests.swift b/Tests/GRPCCodeGenTests/Internal/Translator/IDLToStructuredSwiftTranslatorSnippetBasedTests.swift deleted file mode 100644 index c11dd3bda..000000000 --- a/Tests/GRPCCodeGenTests/Internal/Translator/IDLToStructuredSwiftTranslatorSnippetBasedTests.swift +++ /dev/null @@ -1,649 +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 os(macOS) || os(Linux) // swift-format doesn't like canImport(Foundation.Process) - -import XCTest - -@testable import GRPCCodeGen - -final class IDLToStructuredSwiftTranslatorSnippetBasedTests: XCTestCase { - typealias MethodDescriptor = GRPCCodeGen.CodeGenerationRequest.ServiceDescriptor.MethodDescriptor - typealias ServiceDescriptor = GRPCCodeGen.CodeGenerationRequest.ServiceDescriptor - typealias Name = GRPCCodeGen.CodeGenerationRequest.Name - - func testImports() throws { - var dependencies = [CodeGenerationRequest.Dependency]() - dependencies.append(CodeGenerationRequest.Dependency(module: "Foo", accessLevel: .public)) - dependencies.append( - CodeGenerationRequest.Dependency( - item: .init(kind: .typealias, name: "Bar"), - module: "Foo", - accessLevel: .internal - ) - ) - dependencies.append( - CodeGenerationRequest.Dependency( - item: .init(kind: .struct, name: "Baz"), - module: "Foo", - accessLevel: .package - ) - ) - dependencies.append( - CodeGenerationRequest.Dependency( - item: .init(kind: .class, name: "Bac"), - module: "Foo", - accessLevel: .package - ) - ) - dependencies.append( - CodeGenerationRequest.Dependency( - item: .init(kind: .enum, name: "Bap"), - module: "Foo", - accessLevel: .package - ) - ) - dependencies.append( - CodeGenerationRequest.Dependency( - item: .init(kind: .protocol, name: "Bat"), - module: "Foo", - accessLevel: .package - ) - ) - dependencies.append( - CodeGenerationRequest.Dependency( - item: .init(kind: .let, name: "Baq"), - module: "Foo", - accessLevel: .package - ) - ) - dependencies.append( - CodeGenerationRequest.Dependency( - item: .init(kind: .var, name: "Bag"), - module: "Foo", - accessLevel: .package - ) - ) - dependencies.append( - CodeGenerationRequest.Dependency( - item: .init(kind: .func, name: "Bak"), - module: "Foo", - accessLevel: .package - ) - ) - - let expectedSwift = - """ - /// Some really exciting license header 2023. - - public import GRPCCore - public import Foo - internal import typealias Foo.Bar - package import struct Foo.Baz - package import class Foo.Bac - package import enum Foo.Bap - package import protocol Foo.Bat - package import let Foo.Baq - package import var Foo.Bag - package import func Foo.Bak - - """ - try self.assertIDLToStructuredSwiftTranslation( - codeGenerationRequest: makeCodeGenerationRequest(dependencies: dependencies), - expectedSwift: expectedSwift, - accessLevel: .public - ) - } - - func testPreconcurrencyImports() throws { - var dependencies = [CodeGenerationRequest.Dependency]() - dependencies.append( - CodeGenerationRequest.Dependency( - module: "Foo", - preconcurrency: .required, - accessLevel: .internal - ) - ) - dependencies.append( - CodeGenerationRequest.Dependency( - item: .init(kind: .enum, name: "Bar"), - module: "Foo", - preconcurrency: .required, - accessLevel: .internal - ) - ) - dependencies.append( - CodeGenerationRequest.Dependency( - module: "Baz", - preconcurrency: .requiredOnOS(["Deq", "Der"]), - accessLevel: .internal - ) - ) - let expectedSwift = - """ - /// Some really exciting license header 2023. - - public import GRPCCore - @preconcurrency internal import Foo - @preconcurrency internal import enum Foo.Bar - #if os(Deq) || os(Der) - @preconcurrency internal import Baz - #else - internal import Baz - #endif - - """ - try self.assertIDLToStructuredSwiftTranslation( - codeGenerationRequest: makeCodeGenerationRequest(dependencies: dependencies), - expectedSwift: expectedSwift, - accessLevel: .public - ) - } - - func testSPIImports() throws { - var dependencies = [CodeGenerationRequest.Dependency]() - dependencies.append( - CodeGenerationRequest.Dependency(module: "Foo", spi: "Secret", accessLevel: .internal) - ) - dependencies.append( - CodeGenerationRequest.Dependency( - item: .init(kind: .enum, name: "Bar"), - module: "Foo", - spi: "Secret", - accessLevel: .internal - ) - ) - - let expectedSwift = - """ - /// Some really exciting license header 2023. - - public import GRPCCore - @_spi(Secret) internal import Foo - @_spi(Secret) internal import enum Foo.Bar - - """ - try self.assertIDLToStructuredSwiftTranslation( - codeGenerationRequest: makeCodeGenerationRequest(dependencies: dependencies), - expectedSwift: expectedSwift, - accessLevel: .public - ) - } - - func testGeneration() throws { - var dependencies = [CodeGenerationRequest.Dependency]() - dependencies.append( - CodeGenerationRequest.Dependency(module: "Foo", spi: "Secret", accessLevel: .internal) - ) - dependencies.append( - CodeGenerationRequest.Dependency( - item: .init(kind: .enum, name: "Bar"), - module: "Foo", - spi: "Secret", - accessLevel: .internal - ) - ) - - let serviceA = ServiceDescriptor( - documentation: "/// Documentation for AService\n", - name: Name(base: "ServiceA", generatedUpperCase: "ServiceA", generatedLowerCase: "serviceA"), - namespace: Name( - base: "namespaceA", - generatedUpperCase: "NamespaceA", - generatedLowerCase: "namespaceA" - ), - methods: [] - ) - - let expectedSwift = - """ - /// Some really exciting license header 2023. - - public import GRPCCore - @_spi(Secret) internal import Foo - @_spi(Secret) internal import enum Foo.Bar - - public enum NamespaceA_ServiceA { - public static let descriptor = GRPCCore.ServiceDescriptor.namespaceA_ServiceA - 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 = NamespaceA_ServiceAStreamingServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public typealias ServiceProtocol = NamespaceA_ServiceAServiceProtocol - } - - extension GRPCCore.ServiceDescriptor { - public static let namespaceA_ServiceA = Self( - package: "namespaceA", - service: "ServiceA" - ) - } - - /// Documentation for AService - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public protocol NamespaceA_ServiceAStreamingServiceProtocol: GRPCCore.RegistrableRPCService {} - - /// Conformance to `GRPCCore.RegistrableRPCService`. - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - extension NamespaceA_ServiceA.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) {} - } - - /// Documentation for AService - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public protocol NamespaceA_ServiceAServiceProtocol: NamespaceA_ServiceA.StreamingServiceProtocol {} - - /// Partial conformance to `NamespaceA_ServiceAStreamingServiceProtocol`. - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - extension NamespaceA_ServiceA.ServiceProtocol { - } - """ - try self.assertIDLToStructuredSwiftTranslation( - codeGenerationRequest: makeCodeGenerationRequest( - services: [serviceA], - dependencies: dependencies - ), - expectedSwift: expectedSwift, - accessLevel: .public, - server: true - ) - } - - private func assertIDLToStructuredSwiftTranslation( - codeGenerationRequest: CodeGenerationRequest, - expectedSwift: String, - accessLevel: SourceGenerator.Config.AccessLevel, - server: Bool = false - ) throws { - let translator = IDLToStructuredSwiftTranslator() - let structuredSwift = try translator.translate( - codeGenerationRequest: codeGenerationRequest, - accessLevel: accessLevel, - accessLevelOnImports: true, - client: false, - server: server - ) - let renderer = TextBasedRenderer.default - let sourceFile = try renderer.render(structured: structuredSwift) - let contents = sourceFile.contents - try XCTAssertEqualWithDiff(contents, expectedSwift) - } - - func testSameNameServicesNoNamespaceError() throws { - let serviceA = ServiceDescriptor( - documentation: "Documentation for AService", - name: Name(base: "AService", generatedUpperCase: "AService", generatedLowerCase: "aService"), - namespace: Name(base: "", generatedUpperCase: "", generatedLowerCase: ""), - methods: [] - ) - - let codeGenerationRequest = makeCodeGenerationRequest(services: [serviceA, serviceA]) - let translator = IDLToStructuredSwiftTranslator() - XCTAssertThrowsError( - ofType: CodeGenError.self, - try translator.translate( - codeGenerationRequest: codeGenerationRequest, - accessLevel: .public, - accessLevelOnImports: true, - client: true, - server: true - ) - ) { - error in - XCTAssertEqual( - error as CodeGenError, - CodeGenError( - code: .nonUniqueServiceName, - message: """ - Services must have unique descriptors. \ - AService is the descriptor of at least two different services. - """ - ) - ) - } - } - - func testSameDescriptorsServicesNoNamespaceError() throws { - let serviceA = ServiceDescriptor( - documentation: "Documentation for AService", - name: Name(base: "AService", generatedUpperCase: "AService", generatedLowerCase: "aService"), - namespace: Name(base: "", generatedUpperCase: "", generatedLowerCase: ""), - methods: [] - ) - - let serviceB = ServiceDescriptor( - documentation: "Documentation for BService", - name: Name(base: "AService", generatedUpperCase: "AService", generatedLowerCase: "aService"), - namespace: Name(base: "", generatedUpperCase: "", generatedLowerCase: ""), - methods: [] - ) - - let codeGenerationRequest = makeCodeGenerationRequest(services: [serviceA, serviceB]) - let translator = IDLToStructuredSwiftTranslator() - XCTAssertThrowsError( - ofType: CodeGenError.self, - try translator.translate( - codeGenerationRequest: codeGenerationRequest, - accessLevel: .public, - accessLevelOnImports: true, - client: true, - server: true - ) - ) { - error in - XCTAssertEqual( - error as CodeGenError, - CodeGenError( - code: .nonUniqueServiceName, - message: """ - Services must have unique descriptors. AService is the descriptor of at least two different services. - """ - ) - ) - } - } - func testSameDescriptorsSameNamespaceError() throws { - let serviceA = ServiceDescriptor( - documentation: "Documentation for AService", - name: Name(base: "AService", generatedUpperCase: "AService", generatedLowerCase: "aService"), - namespace: Name( - base: "namespacea", - generatedUpperCase: "NamespaceA", - generatedLowerCase: "namespacea" - ), - methods: [] - ) - - let codeGenerationRequest = makeCodeGenerationRequest(services: [serviceA, serviceA]) - let translator = IDLToStructuredSwiftTranslator() - XCTAssertThrowsError( - ofType: CodeGenError.self, - try translator.translate( - codeGenerationRequest: codeGenerationRequest, - accessLevel: .public, - accessLevelOnImports: true, - client: true, - server: true - ) - ) { - error in - XCTAssertEqual( - error as CodeGenError, - CodeGenError( - code: .nonUniqueServiceName, - message: """ - Services must have unique descriptors. \ - namespacea.AService is the descriptor of at least two different services. - """ - ) - ) - } - } - - func testSameGeneratedNameServicesSameNamespaceError() throws { - let serviceA = ServiceDescriptor( - documentation: "/// Documentation for AService\n", - name: Name(base: "AService", generatedUpperCase: "AService", generatedLowerCase: "aService"), - namespace: Name( - base: "namespacea", - generatedUpperCase: "NamespaceA", - generatedLowerCase: "namespacea" - ), - methods: [] - ) - let serviceB = ServiceDescriptor( - documentation: "/// Documentation for BService\n", - name: Name(base: "BService", generatedUpperCase: "AService", generatedLowerCase: "aService"), - namespace: Name( - base: "namespacea", - generatedUpperCase: "NamespaceA", - generatedLowerCase: "namespacea" - ), - methods: [] - ) - - let codeGenerationRequest = makeCodeGenerationRequest(services: [serviceA, serviceB]) - let translator = IDLToStructuredSwiftTranslator() - XCTAssertThrowsError( - ofType: CodeGenError.self, - try translator.translate( - codeGenerationRequest: codeGenerationRequest, - accessLevel: .internal, - accessLevelOnImports: true, - client: true, - server: true - ) - ) { - error in - XCTAssertEqual( - error as CodeGenError, - CodeGenError( - code: .nonUniqueServiceName, - message: """ - There must be a unique (namespace, service_name) pair for each service. \ - NamespaceA_AService is used as a _ construction for multiple services. - """ - ) - ) - } - } - - func testSameBaseNameMethodsSameServiceError() throws { - let methodA = MethodDescriptor( - documentation: "Documentation for MethodA", - name: Name(base: "MethodA", generatedUpperCase: "MethodA", generatedLowerCase: "methodA"), - isInputStreaming: false, - isOutputStreaming: false, - inputType: "NamespaceA_ServiceARequest", - outputType: "NamespaceA_ServiceAResponse" - ) - let service = ServiceDescriptor( - documentation: "Documentation for AService", - name: Name(base: "AService", generatedUpperCase: "AService", generatedLowerCase: "aService"), - namespace: Name( - base: "namespacea", - generatedUpperCase: "NamespaceA", - generatedLowerCase: "namespacea" - ), - methods: [methodA, methodA] - ) - - let codeGenerationRequest = makeCodeGenerationRequest(services: [service]) - let translator = IDLToStructuredSwiftTranslator() - XCTAssertThrowsError( - ofType: CodeGenError.self, - try translator.translate( - codeGenerationRequest: codeGenerationRequest, - accessLevel: .public, - accessLevelOnImports: true, - client: true, - server: true - ) - ) { - error in - XCTAssertEqual( - error as CodeGenError, - CodeGenError( - code: .nonUniqueMethodName, - message: """ - Methods of a service must have unique base names. \ - MethodA is used as a base name for multiple methods of the AService service. - """ - ) - ) - } - } - - func testSameGeneratedUpperCaseNameMethodsSameServiceError() throws { - let methodA = MethodDescriptor( - documentation: "Documentation for MethodA", - name: Name(base: "MethodA", generatedUpperCase: "MethodA", generatedLowerCase: "methodA"), - isInputStreaming: false, - isOutputStreaming: false, - inputType: "NamespaceA_ServiceARequest", - outputType: "NamespaceA_ServiceAResponse" - ) - let methodB = MethodDescriptor( - documentation: "Documentation for MethodA", - name: Name(base: "MethodB", generatedUpperCase: "MethodA", generatedLowerCase: "methodA"), - isInputStreaming: false, - isOutputStreaming: false, - inputType: "NamespaceA_ServiceARequest", - outputType: "NamespaceA_ServiceAResponse" - ) - let service = ServiceDescriptor( - documentation: "Documentation for AService", - name: Name(base: "AService", generatedUpperCase: "AService", generatedLowerCase: "aService"), - namespace: Name( - base: "namespacea", - generatedUpperCase: "NamespaceA", - generatedLowerCase: "namespacea" - ), - methods: [methodA, methodB] - ) - - let codeGenerationRequest = makeCodeGenerationRequest(services: [service]) - let translator = IDLToStructuredSwiftTranslator() - XCTAssertThrowsError( - ofType: CodeGenError.self, - try translator.translate( - codeGenerationRequest: codeGenerationRequest, - accessLevel: .public, - accessLevelOnImports: true, - client: true, - server: true - ) - ) { - error in - XCTAssertEqual( - error as CodeGenError, - CodeGenError( - code: .nonUniqueMethodName, - message: """ - Methods of a service must have unique generated upper case names. \ - MethodA is used as a generated upper case name for multiple methods of the AService service. - """ - ) - ) - } - } - - func testSameLowerCaseNameMethodsSameServiceError() throws { - let methodA = MethodDescriptor( - documentation: "Documentation for MethodA", - name: Name(base: "MethodA", generatedUpperCase: "MethodA", generatedLowerCase: "methodA"), - isInputStreaming: false, - isOutputStreaming: false, - inputType: "NamespaceA_ServiceARequest", - outputType: "NamespaceA_ServiceAResponse" - ) - let methodB = MethodDescriptor( - documentation: "Documentation for MethodA", - name: Name(base: "MethodB", generatedUpperCase: "MethodB", generatedLowerCase: "methodA"), - isInputStreaming: false, - isOutputStreaming: false, - inputType: "NamespaceA_ServiceARequest", - outputType: "NamespaceA_ServiceAResponse" - ) - let service = ServiceDescriptor( - documentation: "Documentation for AService", - name: Name(base: "AService", generatedUpperCase: "AService", generatedLowerCase: "aService"), - namespace: Name( - base: "namespacea", - generatedUpperCase: "NamespaceA", - generatedLowerCase: "namespacea" - ), - methods: [methodA, methodB] - ) - - let codeGenerationRequest = makeCodeGenerationRequest(services: [service]) - let translator = IDLToStructuredSwiftTranslator() - XCTAssertThrowsError( - ofType: CodeGenError.self, - try translator.translate( - codeGenerationRequest: codeGenerationRequest, - accessLevel: .public, - accessLevelOnImports: true, - client: true, - server: true - ) - ) { - error in - XCTAssertEqual( - error as CodeGenError, - CodeGenError( - code: .nonUniqueMethodName, - message: """ - Methods of a service must have unique lower case names. \ - methodA is used as a signature name for multiple methods of the AService service. - """ - ) - ) - } - } - - func testSameGeneratedNameNoNamespaceServiceAndNamespaceError() throws { - let serviceA = ServiceDescriptor( - documentation: "Documentation for SameName service with no namespace", - name: Name( - base: "SameName", - generatedUpperCase: "SameName_BService", - generatedLowerCase: "sameName" - ), - namespace: Name(base: "", generatedUpperCase: "", generatedLowerCase: ""), - methods: [] - ) - let serviceB = ServiceDescriptor( - documentation: "Documentation for BService", - name: Name(base: "BService", generatedUpperCase: "BService", generatedLowerCase: "bService"), - namespace: Name( - base: "sameName", - generatedUpperCase: "SameName", - generatedLowerCase: "sameName" - ), - methods: [] - ) - let codeGenerationRequest = makeCodeGenerationRequest(services: [serviceA, serviceB]) - let translator = IDLToStructuredSwiftTranslator() - XCTAssertThrowsError( - ofType: CodeGenError.self, - try translator.translate( - codeGenerationRequest: codeGenerationRequest, - accessLevel: .public, - accessLevelOnImports: true, - client: true, - server: true - ) - ) { - error in - XCTAssertEqual( - error as CodeGenError, - CodeGenError( - code: .nonUniqueServiceName, - message: """ - There must be a unique (namespace, service_name) pair for each service. \ - SameName_BService is used as a _ construction for multiple services. - """ - ) - ) - } - } -} - -#endif // os(macOS) || os(Linux) diff --git a/Tests/GRPCCodeGenTests/Internal/Translator/ServerCodeTranslatorSnippetBasedTests.swift b/Tests/GRPCCodeGenTests/Internal/Translator/ServerCodeTranslatorSnippetBasedTests.swift deleted file mode 100644 index d4d2bc571..000000000 --- a/Tests/GRPCCodeGenTests/Internal/Translator/ServerCodeTranslatorSnippetBasedTests.swift +++ /dev/null @@ -1,654 +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 os(macOS) || os(Linux) // swift-format doesn't like canImport(Foundation.Process) - -import XCTest - -@testable import GRPCCodeGen - -final class ServerCodeTranslatorSnippetBasedTests: XCTestCase { - typealias MethodDescriptor = GRPCCodeGen.CodeGenerationRequest.ServiceDescriptor.MethodDescriptor - typealias ServiceDescriptor = GRPCCodeGen.CodeGenerationRequest.ServiceDescriptor - typealias Name = GRPCCodeGen.CodeGenerationRequest.Name - - func testServerCodeTranslatorUnaryMethod() throws { - let method = MethodDescriptor( - documentation: "/// Documentation for unaryMethod", - name: Name(base: "UnaryMethod", generatedUpperCase: "Unary", generatedLowerCase: "unary"), - isInputStreaming: false, - isOutputStreaming: false, - inputType: "NamespaceA_ServiceARequest", - outputType: "NamespaceA_ServiceAResponse" - ) - let service = ServiceDescriptor( - documentation: "/// Documentation for ServiceA", - name: Name( - base: "AlongNameForServiceA", - generatedUpperCase: "ServiceA", - generatedLowerCase: "serviceA" - ), - namespace: Name( - base: "namespaceA", - generatedUpperCase: "NamespaceA", - generatedLowerCase: "namespaceA" - ), - methods: [method] - ) - let expectedSwift = - """ - /// Documentation for ServiceA - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public protocol NamespaceA_ServiceAStreamingServiceProtocol: GRPCCore.RegistrableRPCService { - /// Documentation for unaryMethod - func unary( - 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 NamespaceA_ServiceA.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: NamespaceA_ServiceA.Method.Unary.descriptor, - deserializer: GRPCProtobuf.ProtobufDeserializer(), - serializer: GRPCProtobuf.ProtobufSerializer(), - handler: { request, context in - try await self.unary( - request: request, - context: context - ) - } - ) - } - } - /// Documentation for ServiceA - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public protocol NamespaceA_ServiceAServiceProtocol: NamespaceA_ServiceA.StreamingServiceProtocol { - /// Documentation for unaryMethod - func unary( - request: GRPCCore.ServerRequest.Single, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Single - } - /// Partial conformance to `NamespaceA_ServiceAStreamingServiceProtocol`. - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - extension NamespaceA_ServiceA.ServiceProtocol { - public func unary( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream { - let response = try await self.unary( - request: GRPCCore.ServerRequest.Single(stream: request), - context: context - ) - return GRPCCore.ServerResponse.Stream(single: response) - } - } - """ - - try self.assertServerCodeTranslation( - codeGenerationRequest: makeCodeGenerationRequest(services: [service]), - expectedSwift: expectedSwift, - accessLevel: .public - ) - } - - func testServerCodeTranslatorInputStreamingMethod() throws { - let method = MethodDescriptor( - documentation: "/// Documentation for inputStreamingMethod", - name: Name( - base: "InputStreamingMethod", - generatedUpperCase: "InputStreaming", - generatedLowerCase: "inputStreaming" - ), - isInputStreaming: true, - isOutputStreaming: false, - inputType: "NamespaceA_ServiceARequest", - outputType: "NamespaceA_ServiceAResponse" - ) - let service = ServiceDescriptor( - documentation: "/// Documentation for ServiceA", - name: Name(base: "ServiceA", generatedUpperCase: "ServiceA", generatedLowerCase: "serviceA"), - namespace: Name( - base: "namespaceA", - generatedUpperCase: "NamespaceA", - generatedLowerCase: "namespaceA" - ), - methods: [method] - ) - let expectedSwift = - """ - /// Documentation for ServiceA - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - package protocol NamespaceA_ServiceAStreamingServiceProtocol: GRPCCore.RegistrableRPCService { - /// Documentation for inputStreamingMethod - func inputStreaming( - 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 NamespaceA_ServiceA.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: NamespaceA_ServiceA.Method.InputStreaming.descriptor, - deserializer: GRPCProtobuf.ProtobufDeserializer(), - serializer: GRPCProtobuf.ProtobufSerializer(), - handler: { request, context in - try await self.inputStreaming( - request: request, - context: context - ) - } - ) - } - } - /// Documentation for ServiceA - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - package protocol NamespaceA_ServiceAServiceProtocol: NamespaceA_ServiceA.StreamingServiceProtocol { - /// Documentation for inputStreamingMethod - func inputStreaming( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Single - } - /// Partial conformance to `NamespaceA_ServiceAStreamingServiceProtocol`. - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - extension NamespaceA_ServiceA.ServiceProtocol { - package func inputStreaming( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream { - let response = try await self.inputStreaming( - request: request, - context: context - ) - return GRPCCore.ServerResponse.Stream(single: response) - } - } - """ - - try self.assertServerCodeTranslation( - codeGenerationRequest: makeCodeGenerationRequest(services: [service]), - expectedSwift: expectedSwift, - accessLevel: .package - ) - } - - func testServerCodeTranslatorOutputStreamingMethod() throws { - let method = MethodDescriptor( - documentation: "/// Documentation for outputStreamingMethod", - name: Name( - base: "OutputStreamingMethod", - generatedUpperCase: "OutputStreaming", - generatedLowerCase: "outputStreaming" - ), - isInputStreaming: false, - isOutputStreaming: true, - inputType: "NamespaceA_ServiceARequest", - outputType: "NamespaceA_ServiceAResponse" - ) - let service = ServiceDescriptor( - documentation: "/// Documentation for ServiceA", - name: Name( - base: "ServiceATest", - generatedUpperCase: "ServiceA", - generatedLowerCase: "serviceA" - ), - namespace: Name( - base: "namespaceA", - generatedUpperCase: "NamespaceA", - generatedLowerCase: "namespaceA" - ), - methods: [method] - ) - let expectedSwift = - """ - /// Documentation for ServiceA - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public protocol NamespaceA_ServiceAStreamingServiceProtocol: GRPCCore.RegistrableRPCService { - /// Documentation for outputStreamingMethod - func outputStreaming( - 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 NamespaceA_ServiceA.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: NamespaceA_ServiceA.Method.OutputStreaming.descriptor, - deserializer: GRPCProtobuf.ProtobufDeserializer(), - serializer: GRPCProtobuf.ProtobufSerializer(), - handler: { request, context in - try await self.outputStreaming( - request: request, - context: context - ) - } - ) - } - } - /// Documentation for ServiceA - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public protocol NamespaceA_ServiceAServiceProtocol: NamespaceA_ServiceA.StreamingServiceProtocol { - /// Documentation for outputStreamingMethod - func outputStreaming( - request: GRPCCore.ServerRequest.Single, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream - } - /// Partial conformance to `NamespaceA_ServiceAStreamingServiceProtocol`. - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - extension NamespaceA_ServiceA.ServiceProtocol { - public func outputStreaming( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream { - let response = try await self.outputStreaming( - request: GRPCCore.ServerRequest.Single(stream: request), - context: context - ) - return response - } - } - """ - - try self.assertServerCodeTranslation( - codeGenerationRequest: makeCodeGenerationRequest(services: [service]), - expectedSwift: expectedSwift, - accessLevel: .public - ) - } - - func testServerCodeTranslatorBidirectionalStreamingMethod() throws { - let method = MethodDescriptor( - documentation: "/// Documentation for bidirectionalStreamingMethod", - name: Name( - base: "BidirectionalStreamingMethod", - generatedUpperCase: "BidirectionalStreaming", - generatedLowerCase: "bidirectionalStreaming" - ), - isInputStreaming: true, - isOutputStreaming: true, - inputType: "NamespaceA_ServiceARequest", - outputType: "NamespaceA_ServiceAResponse" - ) - let service = ServiceDescriptor( - documentation: "/// Documentation for ServiceA", - name: Name( - base: "ServiceATest", - generatedUpperCase: "ServiceA", - generatedLowerCase: "serviceA" - ), - namespace: Name( - base: "namespaceA", - generatedUpperCase: "NamespaceA", - generatedLowerCase: "namespaceA" - ), - methods: [method] - ) - let expectedSwift = - """ - /// Documentation for ServiceA - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - package protocol NamespaceA_ServiceAStreamingServiceProtocol: GRPCCore.RegistrableRPCService { - /// Documentation for bidirectionalStreamingMethod - func bidirectionalStreaming( - 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 NamespaceA_ServiceA.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: NamespaceA_ServiceA.Method.BidirectionalStreaming.descriptor, - deserializer: GRPCProtobuf.ProtobufDeserializer(), - serializer: GRPCProtobuf.ProtobufSerializer(), - handler: { request, context in - try await self.bidirectionalStreaming( - request: request, - context: context - ) - } - ) - } - } - /// Documentation for ServiceA - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - package protocol NamespaceA_ServiceAServiceProtocol: NamespaceA_ServiceA.StreamingServiceProtocol { - /// Documentation for bidirectionalStreamingMethod - func bidirectionalStreaming( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream - } - /// Partial conformance to `NamespaceA_ServiceAStreamingServiceProtocol`. - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - extension NamespaceA_ServiceA.ServiceProtocol { - } - """ - - try self.assertServerCodeTranslation( - codeGenerationRequest: makeCodeGenerationRequest(services: [service]), - expectedSwift: expectedSwift, - accessLevel: .package - ) - } - - func testServerCodeTranslatorMultipleMethods() throws { - let inputStreamingMethod = MethodDescriptor( - documentation: "/// Documentation for inputStreamingMethod", - name: Name( - base: "InputStreamingMethod", - generatedUpperCase: "InputStreaming", - generatedLowerCase: "inputStreaming" - ), - isInputStreaming: true, - isOutputStreaming: false, - inputType: "NamespaceA_ServiceARequest", - outputType: "NamespaceA_ServiceAResponse" - ) - let outputStreamingMethod = MethodDescriptor( - documentation: "/// Documentation for outputStreamingMethod", - name: Name( - base: "outputStreamingMethod", - generatedUpperCase: "OutputStreaming", - generatedLowerCase: "outputStreaming" - ), - isInputStreaming: false, - isOutputStreaming: true, - inputType: "NamespaceA_ServiceARequest", - outputType: "NamespaceA_ServiceAResponse" - ) - let service = ServiceDescriptor( - documentation: "/// Documentation for ServiceA", - name: Name( - base: "ServiceATest", - generatedUpperCase: "ServiceA", - generatedLowerCase: "serviceA" - ), - namespace: Name( - base: "namespaceA", - generatedUpperCase: "NamespaceA", - generatedLowerCase: "namespaceA" - ), - methods: [inputStreamingMethod, outputStreamingMethod] - ) - let expectedSwift = - """ - /// Documentation for ServiceA - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - internal protocol NamespaceA_ServiceAStreamingServiceProtocol: GRPCCore.RegistrableRPCService { - /// Documentation for inputStreamingMethod - func inputStreaming( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream - - /// Documentation for outputStreamingMethod - func outputStreaming( - 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 NamespaceA_ServiceA.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: NamespaceA_ServiceA.Method.InputStreaming.descriptor, - deserializer: GRPCProtobuf.ProtobufDeserializer(), - serializer: GRPCProtobuf.ProtobufSerializer(), - handler: { request, context in - try await self.inputStreaming( - request: request, - context: context - ) - } - ) - router.registerHandler( - forMethod: NamespaceA_ServiceA.Method.OutputStreaming.descriptor, - deserializer: GRPCProtobuf.ProtobufDeserializer(), - serializer: GRPCProtobuf.ProtobufSerializer(), - handler: { request, context in - try await self.outputStreaming( - request: request, - context: context - ) - } - ) - } - } - /// Documentation for ServiceA - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - internal protocol NamespaceA_ServiceAServiceProtocol: NamespaceA_ServiceA.StreamingServiceProtocol { - /// Documentation for inputStreamingMethod - func inputStreaming( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Single - - /// Documentation for outputStreamingMethod - func outputStreaming( - request: GRPCCore.ServerRequest.Single, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream - } - /// Partial conformance to `NamespaceA_ServiceAStreamingServiceProtocol`. - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - extension NamespaceA_ServiceA.ServiceProtocol { - internal func inputStreaming( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream { - let response = try await self.inputStreaming( - request: request, - context: context - ) - return GRPCCore.ServerResponse.Stream(single: response) - } - - internal func outputStreaming( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream { - let response = try await self.outputStreaming( - request: GRPCCore.ServerRequest.Single(stream: request), - context: context - ) - return response - } - } - """ - - try assertServerCodeTranslation( - codeGenerationRequest: makeCodeGenerationRequest(services: [service]), - expectedSwift: expectedSwift, - accessLevel: .internal - ) - } - - func testServerCodeTranslatorNoNamespaceService() throws { - let method = MethodDescriptor( - documentation: "/// Documentation for MethodA", - name: Name(base: "methodA", generatedUpperCase: "MethodA", generatedLowerCase: "methodA"), - isInputStreaming: false, - isOutputStreaming: false, - inputType: "NamespaceA_ServiceARequest", - outputType: "NamespaceA_ServiceAResponse" - ) - let service = ServiceDescriptor( - documentation: "/// Documentation for ServiceA", - name: Name( - base: "ServiceATest", - generatedUpperCase: "ServiceA", - generatedLowerCase: "serviceA" - ), - namespace: Name(base: "", generatedUpperCase: "", generatedLowerCase: ""), - methods: [method] - ) - let expectedSwift = - """ - /// Documentation for ServiceA - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - internal protocol ServiceAStreamingServiceProtocol: GRPCCore.RegistrableRPCService { - /// Documentation for MethodA - func methodA( - 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 ServiceA.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: ServiceA.Method.MethodA.descriptor, - deserializer: GRPCProtobuf.ProtobufDeserializer(), - serializer: GRPCProtobuf.ProtobufSerializer(), - handler: { request, context in - try await self.methodA( - request: request, - context: context - ) - } - ) - } - } - /// Documentation for ServiceA - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - internal protocol ServiceAServiceProtocol: ServiceA.StreamingServiceProtocol { - /// Documentation for MethodA - func methodA( - request: GRPCCore.ServerRequest.Single, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Single - } - /// Partial conformance to `ServiceAStreamingServiceProtocol`. - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - extension ServiceA.ServiceProtocol { - internal func methodA( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream { - let response = try await self.methodA( - request: GRPCCore.ServerRequest.Single(stream: request), - context: context - ) - return GRPCCore.ServerResponse.Stream(single: response) - } - } - """ - - try self.assertServerCodeTranslation( - codeGenerationRequest: makeCodeGenerationRequest(services: [service]), - expectedSwift: expectedSwift, - accessLevel: .internal - ) - } - - func testServerCodeTranslatorMoreServicesOrder() throws { - let serviceA = ServiceDescriptor( - documentation: "/// Documentation for ServiceA", - name: Name(base: "ServiceA", generatedUpperCase: "ServiceA", generatedLowerCase: "serviceA"), - namespace: Name( - base: "namespaceA", - generatedUpperCase: "NamespaceA", - generatedLowerCase: "namespaceA" - ), - methods: [] - ) - let serviceB = ServiceDescriptor( - documentation: "/// Documentation for ServiceB", - name: Name(base: "ServiceB", generatedUpperCase: "ServiceB", generatedLowerCase: "serviceB"), - namespace: Name( - base: "namespaceA", - generatedUpperCase: "NamespaceA", - generatedLowerCase: "namespaceA" - ), - methods: [] - ) - let expectedSwift = - """ - /// Documentation for ServiceA - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public protocol NamespaceA_ServiceAStreamingServiceProtocol: GRPCCore.RegistrableRPCService {} - /// Conformance to `GRPCCore.RegistrableRPCService`. - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - extension NamespaceA_ServiceA.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) {} - } - /// Documentation for ServiceA - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public protocol NamespaceA_ServiceAServiceProtocol: NamespaceA_ServiceA.StreamingServiceProtocol {} - /// Partial conformance to `NamespaceA_ServiceAStreamingServiceProtocol`. - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - extension NamespaceA_ServiceA.ServiceProtocol { - } - /// Documentation for ServiceB - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public protocol NamespaceA_ServiceBStreamingServiceProtocol: GRPCCore.RegistrableRPCService {} - /// Conformance to `GRPCCore.RegistrableRPCService`. - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - extension NamespaceA_ServiceB.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) {} - } - /// Documentation for ServiceB - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public protocol NamespaceA_ServiceBServiceProtocol: NamespaceA_ServiceB.StreamingServiceProtocol {} - /// Partial conformance to `NamespaceA_ServiceBStreamingServiceProtocol`. - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - extension NamespaceA_ServiceB.ServiceProtocol { - } - """ - - try self.assertServerCodeTranslation( - codeGenerationRequest: makeCodeGenerationRequest(services: [serviceA, serviceB]), - expectedSwift: expectedSwift, - accessLevel: .public - ) - } - - private func assertServerCodeTranslation( - codeGenerationRequest: CodeGenerationRequest, - expectedSwift: String, - accessLevel: SourceGenerator.Config.AccessLevel - ) throws { - let translator = ServerCodeTranslator(accessLevel: accessLevel) - let codeBlocks = try translator.translate(from: codeGenerationRequest) - let renderer = TextBasedRenderer.default - renderer.renderCodeBlocks(codeBlocks) - let contents = renderer.renderedContents() - try XCTAssertEqualWithDiff(contents, expectedSwift) - } -} - -#endif // os(macOS) || os(Linux) diff --git a/Tests/GRPCCodeGenTests/Internal/Translator/TestFunctions.swift b/Tests/GRPCCodeGenTests/Internal/Translator/TestFunctions.swift deleted file mode 100644 index 52ab821a1..000000000 --- a/Tests/GRPCCodeGenTests/Internal/Translator/TestFunctions.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 -// -//===----------------------------------------------------------------------===// - -#if os(macOS) || os(Linux) // swift-format doesn't like canImport(Foundation.Process) - -import XCTest - -import GRPCCodeGen - -private func diff(expected: String, actual: String) throws -> String { - let process = Process() - process.executableURL = URL(fileURLWithPath: "/usr/bin/env") - process.arguments = [ - "bash", "-c", - "diff -U5 --label=expected <(echo '\(expected)') --label=actual <(echo '\(actual)')", - ] - let pipe = Pipe() - process.standardOutput = pipe - try process.run() - process.waitUntilExit() - let pipeData = try XCTUnwrap( - pipe.fileHandleForReading.readToEnd(), - """ - No output from command: - \(process.executableURL!.path) \(process.arguments!.joined(separator: " ")) - """ - ) - return String(decoding: pipeData, as: UTF8.self) -} - -internal func XCTAssertEqualWithDiff( - _ actual: String, - _ expected: String, - file: StaticString = #filePath, - line: UInt = #line -) throws { - if actual == expected { return } - XCTFail( - """ - XCTAssertEqualWithDiff failed (click for diff) - \(try diff(expected: expected, actual: actual)) - """, - file: file, - line: line - ) -} - -internal func makeCodeGenerationRequest( - services: [CodeGenerationRequest.ServiceDescriptor] = [], - dependencies: [CodeGenerationRequest.Dependency] = [] -) -> CodeGenerationRequest { - return CodeGenerationRequest( - fileName: "test.grpc", - leadingTrivia: "/// Some really exciting license header 2023.\n", - dependencies: dependencies, - services: services, - lookupSerializer: { - "GRPCProtobuf.ProtobufSerializer<\($0)>()" - }, - lookupDeserializer: { - "GRPCProtobuf.ProtobufDeserializer<\($0)>()" - } - ) -} - -internal func XCTAssertThrowsError( - ofType: E.Type, - _ expression: @autoclosure () throws -> T, - _ errorHandler: (E) -> Void -) { - XCTAssertThrowsError(try expression()) { error in - guard let error = error as? E else { - return XCTFail("Error had unexpected type '\(type(of: error))'") - } - errorHandler(error) - } -} - -#endif // os(macOS) || os(Linux) diff --git a/Tests/GRPCCodeGenTests/Internal/Translator/TypealiasTranslatorSnippetBasedTests.swift b/Tests/GRPCCodeGenTests/Internal/Translator/TypealiasTranslatorSnippetBasedTests.swift deleted file mode 100644 index af0a9017a..000000000 --- a/Tests/GRPCCodeGenTests/Internal/Translator/TypealiasTranslatorSnippetBasedTests.swift +++ /dev/null @@ -1,739 +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 os(macOS) || os(Linux) // swift-format doesn't like canImport(Foundation.Process) - -import XCTest - -@testable import GRPCCodeGen - -final class TypealiasTranslatorSnippetBasedTests: XCTestCase { - typealias MethodDescriptor = GRPCCodeGen.CodeGenerationRequest.ServiceDescriptor.MethodDescriptor - typealias ServiceDescriptor = GRPCCodeGen.CodeGenerationRequest.ServiceDescriptor - typealias Name = GRPCCodeGen.CodeGenerationRequest.Name - - func testTypealiasTranslator() throws { - let method = MethodDescriptor( - documentation: "Documentation for MethodA", - name: Name(base: "MethodA", generatedUpperCase: "MethodA", generatedLowerCase: "methodA"), - isInputStreaming: false, - isOutputStreaming: false, - inputType: "NamespaceA_ServiceARequest", - outputType: "NamespaceA_ServiceAResponse" - ) - let service = ServiceDescriptor( - documentation: "Documentation for ServiceA", - name: Name(base: "ServiceA", generatedUpperCase: "ServiceA", generatedLowerCase: "serviceA"), - namespace: Name( - base: "namespaceA", - generatedUpperCase: "NamespaceA", - generatedLowerCase: "namespaceA" - ), - methods: [method] - ) - let expectedSwift = - """ - public enum NamespaceA_ServiceA { - public static let descriptor = GRPCCore.ServiceDescriptor.namespaceA_ServiceA - public enum Method { - public enum MethodA { - public typealias Input = NamespaceA_ServiceARequest - public typealias Output = NamespaceA_ServiceAResponse - public static let descriptor = GRPCCore.MethodDescriptor( - service: NamespaceA_ServiceA.descriptor.fullyQualifiedService, - method: "MethodA" - ) - } - public static let descriptors: [GRPCCore.MethodDescriptor] = [ - MethodA.descriptor - ] - } - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public typealias StreamingServiceProtocol = NamespaceA_ServiceAStreamingServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public typealias ServiceProtocol = NamespaceA_ServiceAServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public typealias ClientProtocol = NamespaceA_ServiceAClientProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public typealias Client = NamespaceA_ServiceAClient - } - extension GRPCCore.ServiceDescriptor { - public static let namespaceA_ServiceA = Self( - package: "namespaceA", - service: "ServiceA" - ) - } - """ - - try self.assertTypealiasTranslation( - codeGenerationRequest: makeCodeGenerationRequest(services: [service]), - expectedSwift: expectedSwift, - client: true, - server: true, - accessLevel: .public - ) - } - - func testTypealiasTranslatorNoMethodsServiceClientAndServer() throws { - let service = ServiceDescriptor( - documentation: "Documentation for ServiceA", - name: Name(base: "ServiceA", generatedUpperCase: "ServiceA", generatedLowerCase: "serviceA"), - namespace: Name( - base: "namespaceA", - generatedUpperCase: "NamespaceA", - generatedLowerCase: "namespaceA" - ), - methods: [] - ) - let expectedSwift = - """ - public enum NamespaceA_ServiceA { - public static let descriptor = GRPCCore.ServiceDescriptor.namespaceA_ServiceA - 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 = NamespaceA_ServiceAStreamingServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public typealias ServiceProtocol = NamespaceA_ServiceAServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public typealias ClientProtocol = NamespaceA_ServiceAClientProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public typealias Client = NamespaceA_ServiceAClient - } - extension GRPCCore.ServiceDescriptor { - public static let namespaceA_ServiceA = Self( - package: "namespaceA", - service: "ServiceA" - ) - } - """ - - try self.assertTypealiasTranslation( - codeGenerationRequest: makeCodeGenerationRequest(services: [service]), - expectedSwift: expectedSwift, - client: true, - server: true, - accessLevel: .public - ) - } - - func testTypealiasTranslatorServer() throws { - let service = ServiceDescriptor( - documentation: "Documentation for ServiceA", - name: Name(base: "ServiceA", generatedUpperCase: "ServiceA", generatedLowerCase: "serviceA"), - namespace: Name( - base: "namespaceA", - generatedUpperCase: "NamespaceA", - generatedLowerCase: "namespaceA" - ), - methods: [] - ) - let expectedSwift = - """ - public enum NamespaceA_ServiceA { - public static let descriptor = GRPCCore.ServiceDescriptor.namespaceA_ServiceA - 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 = NamespaceA_ServiceAStreamingServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public typealias ServiceProtocol = NamespaceA_ServiceAServiceProtocol - } - extension GRPCCore.ServiceDescriptor { - public static let namespaceA_ServiceA = Self( - package: "namespaceA", - service: "ServiceA" - ) - } - """ - - try self.assertTypealiasTranslation( - codeGenerationRequest: makeCodeGenerationRequest(services: [service]), - expectedSwift: expectedSwift, - client: false, - server: true, - accessLevel: .public - ) - } - - func testTypealiasTranslatorClient() throws { - let service = ServiceDescriptor( - documentation: "Documentation for ServiceA", - name: Name(base: "ServiceA", generatedUpperCase: "ServiceA", generatedLowerCase: "serviceA"), - namespace: Name( - base: "namespaceA", - generatedUpperCase: "NamespaceA", - generatedLowerCase: "namespaceA" - ), - methods: [] - ) - let expectedSwift = - """ - public enum NamespaceA_ServiceA { - public static let descriptor = GRPCCore.ServiceDescriptor.namespaceA_ServiceA - 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 ClientProtocol = NamespaceA_ServiceAClientProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public typealias Client = NamespaceA_ServiceAClient - } - extension GRPCCore.ServiceDescriptor { - public static let namespaceA_ServiceA = Self( - package: "namespaceA", - service: "ServiceA" - ) - } - """ - - try self.assertTypealiasTranslation( - codeGenerationRequest: makeCodeGenerationRequest(services: [service]), - expectedSwift: expectedSwift, - client: true, - server: false, - accessLevel: .public - ) - } - - func testTypealiasTranslatorNoClientNoServer() throws { - let service = ServiceDescriptor( - documentation: "Documentation for ServiceA", - name: Name(base: "ServiceA", generatedUpperCase: "ServiceA", generatedLowerCase: "serviceA"), - namespace: Name( - base: "namespaceA", - generatedUpperCase: "NamespaceA", - generatedLowerCase: "namespaceA" - ), - methods: [] - ) - let expectedSwift = - """ - public enum NamespaceA_ServiceA { - public static let descriptor = GRPCCore.ServiceDescriptor.namespaceA_ServiceA - public enum Method { - public static let descriptors: [GRPCCore.MethodDescriptor] = [] - } - } - extension GRPCCore.ServiceDescriptor { - public static let namespaceA_ServiceA = Self( - package: "namespaceA", - service: "ServiceA" - ) - } - """ - - try self.assertTypealiasTranslation( - codeGenerationRequest: makeCodeGenerationRequest(services: [service]), - expectedSwift: expectedSwift, - client: false, - server: false, - accessLevel: .public - ) - } - - func testTypealiasTranslatorEmptyNamespace() throws { - let method = MethodDescriptor( - documentation: "Documentation for MethodA", - name: Name(base: "MethodA", generatedUpperCase: "MethodA", generatedLowerCase: "methodA"), - isInputStreaming: false, - isOutputStreaming: false, - inputType: "ServiceARequest", - outputType: "ServiceAResponse" - ) - let service = ServiceDescriptor( - documentation: "Documentation for ServiceA", - name: Name(base: "ServiceA", generatedUpperCase: "ServiceA", generatedLowerCase: "serviceA"), - namespace: Name(base: "", generatedUpperCase: "", generatedLowerCase: ""), - methods: [method] - ) - let expectedSwift = - """ - public enum ServiceA { - public static let descriptor = GRPCCore.ServiceDescriptor.ServiceA - public enum Method { - public enum MethodA { - public typealias Input = ServiceARequest - public typealias Output = ServiceAResponse - public static let descriptor = GRPCCore.MethodDescriptor( - service: ServiceA.descriptor.fullyQualifiedService, - method: "MethodA" - ) - } - public static let descriptors: [GRPCCore.MethodDescriptor] = [ - MethodA.descriptor - ] - } - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public typealias StreamingServiceProtocol = ServiceAStreamingServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public typealias ServiceProtocol = ServiceAServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public typealias ClientProtocol = ServiceAClientProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public typealias Client = ServiceAClient - } - extension GRPCCore.ServiceDescriptor { - public static let ServiceA = Self( - package: "", - service: "ServiceA" - ) - } - """ - - try self.assertTypealiasTranslation( - codeGenerationRequest: makeCodeGenerationRequest(services: [service]), - expectedSwift: expectedSwift, - client: true, - server: true, - accessLevel: .public - ) - } - - func testTypealiasTranslatorCheckMethodsOrder() throws { - let methodA = MethodDescriptor( - documentation: "Documentation for MethodA", - name: Name(base: "MethodA", generatedUpperCase: "MethodA", generatedLowerCase: "methodA"), - isInputStreaming: false, - isOutputStreaming: false, - inputType: "NamespaceA_ServiceARequest", - outputType: "NamespaceA_ServiceAResponse" - ) - let methodB = MethodDescriptor( - documentation: "Documentation for MethodB", - name: Name(base: "MethodB", generatedUpperCase: "MethodB", generatedLowerCase: "methodB"), - isInputStreaming: false, - isOutputStreaming: false, - inputType: "NamespaceA_ServiceARequest", - outputType: "NamespaceA_ServiceAResponse" - ) - let service = ServiceDescriptor( - documentation: "Documentation for ServiceA", - name: Name(base: "ServiceA", generatedUpperCase: "ServiceA", generatedLowerCase: "serviceA"), - namespace: Name( - base: "namespaceA", - generatedUpperCase: "NamespaceA", - generatedLowerCase: "namespaceA" - ), - methods: [methodA, methodB] - ) - let expectedSwift = - """ - public enum NamespaceA_ServiceA { - public static let descriptor = GRPCCore.ServiceDescriptor.namespaceA_ServiceA - public enum Method { - public enum MethodA { - public typealias Input = NamespaceA_ServiceARequest - public typealias Output = NamespaceA_ServiceAResponse - public static let descriptor = GRPCCore.MethodDescriptor( - service: NamespaceA_ServiceA.descriptor.fullyQualifiedService, - method: "MethodA" - ) - } - public enum MethodB { - public typealias Input = NamespaceA_ServiceARequest - public typealias Output = NamespaceA_ServiceAResponse - public static let descriptor = GRPCCore.MethodDescriptor( - service: NamespaceA_ServiceA.descriptor.fullyQualifiedService, - method: "MethodB" - ) - } - public static let descriptors: [GRPCCore.MethodDescriptor] = [ - MethodA.descriptor, - MethodB.descriptor - ] - } - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public typealias StreamingServiceProtocol = NamespaceA_ServiceAStreamingServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public typealias ServiceProtocol = NamespaceA_ServiceAServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public typealias ClientProtocol = NamespaceA_ServiceAClientProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public typealias Client = NamespaceA_ServiceAClient - } - extension GRPCCore.ServiceDescriptor { - public static let namespaceA_ServiceA = Self( - package: "namespaceA", - service: "ServiceA" - ) - } - """ - - try self.assertTypealiasTranslation( - codeGenerationRequest: makeCodeGenerationRequest(services: [service]), - expectedSwift: expectedSwift, - client: true, - server: true, - accessLevel: .public - ) - } - - func testTypealiasTranslatorNoMethodsService() throws { - let service = ServiceDescriptor( - documentation: "Documentation for ServiceA", - name: Name(base: "ServiceA", generatedUpperCase: "ServiceA", generatedLowerCase: "serviceA"), - namespace: Name( - base: "namespaceA", - generatedUpperCase: "NamespaceA", - generatedLowerCase: "namespaceA" - ), - methods: [] - ) - let expectedSwift = - """ - package enum NamespaceA_ServiceA { - package static let descriptor = GRPCCore.ServiceDescriptor.namespaceA_ServiceA - package enum Method { - package static let descriptors: [GRPCCore.MethodDescriptor] = [] - } - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - package typealias StreamingServiceProtocol = NamespaceA_ServiceAStreamingServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - package typealias ServiceProtocol = NamespaceA_ServiceAServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - package typealias ClientProtocol = NamespaceA_ServiceAClientProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - package typealias Client = NamespaceA_ServiceAClient - } - extension GRPCCore.ServiceDescriptor { - package static let namespaceA_ServiceA = Self( - package: "namespaceA", - service: "ServiceA" - ) - } - """ - - try self.assertTypealiasTranslation( - codeGenerationRequest: makeCodeGenerationRequest(services: [service]), - expectedSwift: expectedSwift, - client: true, - server: true, - accessLevel: .package - ) - } - - func testTypealiasTranslatorServiceAlphabeticalOrder() throws { - let serviceB = ServiceDescriptor( - documentation: "Documentation for BService", - name: Name(base: "BService", generatedUpperCase: "Bservice", generatedLowerCase: "bservice"), - namespace: Name( - base: "namespaceA", - generatedUpperCase: "NamespaceA", - generatedLowerCase: "namespaceA" - ), - methods: [] - ) - - let serviceA = ServiceDescriptor( - documentation: "Documentation for AService", - name: Name(base: "AService", generatedUpperCase: "Aservice", generatedLowerCase: "aservice"), - namespace: Name( - base: "namespaceA", - generatedUpperCase: "NamespaceA", - generatedLowerCase: "namespaceA" - ), - methods: [] - ) - - let expectedSwift = - """ - public enum NamespaceA_Aservice { - public static let descriptor = GRPCCore.ServiceDescriptor.namespaceA_AService - 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 = NamespaceA_AserviceStreamingServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public typealias ServiceProtocol = NamespaceA_AserviceServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public typealias ClientProtocol = NamespaceA_AserviceClientProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public typealias Client = NamespaceA_AserviceClient - } - extension GRPCCore.ServiceDescriptor { - public static let namespaceA_AService = Self( - package: "namespaceA", - service: "AService" - ) - } - public enum NamespaceA_Bservice { - public static let descriptor = GRPCCore.ServiceDescriptor.namespaceA_BService - 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 = NamespaceA_BserviceStreamingServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public typealias ServiceProtocol = NamespaceA_BserviceServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public typealias ClientProtocol = NamespaceA_BserviceClientProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public typealias Client = NamespaceA_BserviceClient - } - extension GRPCCore.ServiceDescriptor { - public static let namespaceA_BService = Self( - package: "namespaceA", - service: "BService" - ) - } - """ - - try self.assertTypealiasTranslation( - codeGenerationRequest: makeCodeGenerationRequest(services: [serviceB, serviceA]), - expectedSwift: expectedSwift, - client: true, - server: true, - accessLevel: .public - ) - } - - func testTypealiasTranslatorServiceAlphabeticalOrderNoNamespace() throws { - let serviceB = ServiceDescriptor( - documentation: "Documentation for BService", - name: Name(base: "BService", generatedUpperCase: "BService", generatedLowerCase: "bservice"), - namespace: Name(base: "", generatedUpperCase: "", generatedLowerCase: ""), - methods: [] - ) - - let serviceA = ServiceDescriptor( - documentation: "Documentation for AService", - name: Name(base: "AService", generatedUpperCase: "AService", generatedLowerCase: "aservice"), - namespace: Name(base: "", generatedUpperCase: "", generatedLowerCase: ""), - methods: [] - ) - - let expectedSwift = - """ - package enum AService { - package static let descriptor = GRPCCore.ServiceDescriptor.AService - package enum Method { - package static let descriptors: [GRPCCore.MethodDescriptor] = [] - } - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - package typealias StreamingServiceProtocol = AServiceStreamingServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - package typealias ServiceProtocol = AServiceServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - package typealias ClientProtocol = AServiceClientProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - package typealias Client = AServiceClient - } - extension GRPCCore.ServiceDescriptor { - package static let AService = Self( - package: "", - service: "AService" - ) - } - package enum BService { - package static let descriptor = GRPCCore.ServiceDescriptor.BService - package enum Method { - package static let descriptors: [GRPCCore.MethodDescriptor] = [] - } - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - package typealias StreamingServiceProtocol = BServiceStreamingServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - package typealias ServiceProtocol = BServiceServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - package typealias ClientProtocol = BServiceClientProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - package typealias Client = BServiceClient - } - extension GRPCCore.ServiceDescriptor { - package static let BService = Self( - package: "", - service: "BService" - ) - } - """ - - try self.assertTypealiasTranslation( - codeGenerationRequest: makeCodeGenerationRequest(services: [serviceB, serviceA]), - expectedSwift: expectedSwift, - client: true, - server: true, - accessLevel: .package - ) - } - - func testTypealiasTranslatorNamespaceAlphabeticalOrder() throws { - let serviceB = ServiceDescriptor( - documentation: "Documentation for BService", - name: Name(base: "BService", generatedUpperCase: "BService", generatedLowerCase: "bservice"), - namespace: Name( - base: "bnamespace", - generatedUpperCase: "Bnamespace", - generatedLowerCase: "bnamespace" - ), - methods: [] - ) - - let serviceA = ServiceDescriptor( - documentation: "Documentation for AService", - name: Name(base: "AService", generatedUpperCase: "AService", generatedLowerCase: "aservice"), - namespace: Name( - base: "anamespace", - generatedUpperCase: "Anamespace", - generatedLowerCase: "anamespace" - ), - methods: [] - ) - - let expectedSwift = - """ - internal enum Anamespace_AService { - internal static let descriptor = GRPCCore.ServiceDescriptor.anamespace_AService - internal enum Method { - internal static let descriptors: [GRPCCore.MethodDescriptor] = [] - } - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - internal typealias StreamingServiceProtocol = Anamespace_AServiceStreamingServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - internal typealias ServiceProtocol = Anamespace_AServiceServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - internal typealias ClientProtocol = Anamespace_AServiceClientProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - internal typealias Client = Anamespace_AServiceClient - } - extension GRPCCore.ServiceDescriptor { - internal static let anamespace_AService = Self( - package: "anamespace", - service: "AService" - ) - } - internal enum Bnamespace_BService { - internal static let descriptor = GRPCCore.ServiceDescriptor.bnamespace_BService - internal enum Method { - internal static let descriptors: [GRPCCore.MethodDescriptor] = [] - } - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - internal typealias StreamingServiceProtocol = Bnamespace_BServiceStreamingServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - internal typealias ServiceProtocol = Bnamespace_BServiceServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - internal typealias ClientProtocol = Bnamespace_BServiceClientProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - internal typealias Client = Bnamespace_BServiceClient - } - extension GRPCCore.ServiceDescriptor { - internal static let bnamespace_BService = Self( - package: "bnamespace", - service: "BService" - ) - } - """ - - try self.assertTypealiasTranslation( - codeGenerationRequest: makeCodeGenerationRequest(services: [serviceB, serviceA]), - expectedSwift: expectedSwift, - client: true, - server: true, - accessLevel: .internal - ) - } - - func testTypealiasTranslatorNamespaceNoNamespaceOrder() throws { - let serviceA = ServiceDescriptor( - documentation: "Documentation for AService", - name: Name(base: "AService", generatedUpperCase: "AService", generatedLowerCase: "aService"), - namespace: Name( - base: "anamespace", - generatedUpperCase: "Anamespace", - generatedLowerCase: "anamespace" - ), - methods: [] - ) - let serviceB = ServiceDescriptor( - documentation: "Documentation for BService", - name: Name(base: "BService", generatedUpperCase: "BService", generatedLowerCase: "bService"), - namespace: Name(base: "", generatedUpperCase: "", generatedLowerCase: ""), - methods: [] - ) - let expectedSwift = - """ - public enum Anamespace_AService { - public static let descriptor = GRPCCore.ServiceDescriptor.anamespace_AService - 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 = Anamespace_AServiceStreamingServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public typealias ServiceProtocol = Anamespace_AServiceServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public typealias ClientProtocol = Anamespace_AServiceClientProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public typealias Client = Anamespace_AServiceClient - } - extension GRPCCore.ServiceDescriptor { - public static let anamespace_AService = Self( - package: "anamespace", - service: "AService" - ) - } - public enum BService { - public static let descriptor = GRPCCore.ServiceDescriptor.BService - 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 = BServiceStreamingServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public typealias ServiceProtocol = BServiceServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public typealias ClientProtocol = BServiceClientProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public typealias Client = BServiceClient - } - extension GRPCCore.ServiceDescriptor { - public static let BService = Self( - package: "", - service: "BService" - ) - } - """ - - try self.assertTypealiasTranslation( - codeGenerationRequest: makeCodeGenerationRequest(services: [serviceA, serviceB]), - expectedSwift: expectedSwift, - client: true, - server: true, - accessLevel: .public - ) - } -} - -extension TypealiasTranslatorSnippetBasedTests { - private func assertTypealiasTranslation( - codeGenerationRequest: CodeGenerationRequest, - expectedSwift: String, - client: Bool, - server: Bool, - accessLevel: SourceGenerator.Config.AccessLevel - ) throws { - let translator = TypealiasTranslator(client: client, server: server, accessLevel: accessLevel) - let codeBlocks = try translator.translate(from: codeGenerationRequest) - let renderer = TextBasedRenderer.default - renderer.renderCodeBlocks(codeBlocks) - let contents = renderer.renderedContents() - try XCTAssertEqualWithDiff(contents, expectedSwift) - } -} - -#endif // os(macOS) || os(Linux) diff --git a/Tests/GRPCCoreTests/Call/Client/ClientRequestTests.swift b/Tests/GRPCCoreTests/Call/Client/ClientRequestTests.swift deleted file mode 100644 index 7d0304260..000000000 --- a/Tests/GRPCCoreTests/Call/Client/ClientRequestTests.swift +++ /dev/null @@ -1,34 +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. - */ - -import XCTest - -@testable import GRPCCore - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -final class ClientRequestTests: XCTestCase { - func testSingleToStreamConversion() async throws { - let (messages, continuation) = AsyncStream.makeStream(of: String.self) - let single = ClientRequest.Single(message: "foo", metadata: ["bar": "baz"]) - let stream = ClientRequest.Stream(single: single) - - XCTAssertEqual(stream.metadata, ["bar": "baz"]) - try await stream.producer(.gathering(into: continuation)) - continuation.finish() - let collected = try await messages.collect() - XCTAssertEqual(collected, ["foo"]) - } -} diff --git a/Tests/GRPCCoreTests/Call/Client/ClientResponseTests.swift b/Tests/GRPCCoreTests/Call/Client/ClientResponseTests.swift deleted file mode 100644 index a284112be..000000000 --- a/Tests/GRPCCoreTests/Call/Client/ClientResponseTests.swift +++ /dev/null @@ -1,185 +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. - */ - -import XCTest - -@testable import GRPCCore - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -final class ClientResponseTests: XCTestCase { - func testAcceptedSingleResponseConvenienceMethods() { - let response = ClientResponse.Single( - message: "message", - metadata: ["foo": "bar"], - trailingMetadata: ["bar": "baz"] - ) - - XCTAssertEqual(response.metadata, ["foo": "bar"]) - XCTAssertEqual(try response.message, "message") - XCTAssertEqual(response.trailingMetadata, ["bar": "baz"]) - } - - func testRejectedSingleResponseConvenienceMethods() { - let error = RPCError(code: .aborted, message: "error message", metadata: ["bar": "baz"]) - let response = ClientResponse.Single(of: String.self, error: error) - - XCTAssertEqual(response.metadata, [:]) - XCTAssertThrowsRPCError(try response.message) { - XCTAssertEqual($0, error) - } - XCTAssertEqual(response.trailingMetadata, ["bar": "baz"]) - } - - func testAcceptedButFailedSingleResponseConvenienceMethods() { - let error = RPCError(code: .aborted, message: "error message", metadata: ["bar": "baz"]) - let response = ClientResponse.Single(of: String.self, metadata: ["foo": "bar"], error: error) - - XCTAssertEqual(response.metadata, ["foo": "bar"]) - XCTAssertThrowsRPCError(try response.message) { - XCTAssertEqual($0, error) - } - XCTAssertEqual(response.trailingMetadata, ["bar": "baz"]) - } - - func testAcceptedStreamResponseConvenienceMethods() async throws { - let response = ClientResponse.Stream( - of: String.self, - metadata: ["foo": "bar"], - bodyParts: RPCAsyncSequence( - wrapping: AsyncThrowingStream { - $0.yield(.message("foo")) - $0.yield(.message("bar")) - $0.yield(.message("baz")) - $0.yield(.trailingMetadata(["baz": "baz"])) - $0.finish() - } - ) - ) - - XCTAssertEqual(response.metadata, ["foo": "bar"]) - let messages = try await response.messages.collect() - XCTAssertEqual(messages, ["foo", "bar", "baz"]) - } - - func testRejectedStreamResponseConvenienceMethods() async throws { - let error = RPCError(code: .aborted, message: "error message", metadata: ["bar": "baz"]) - let response = ClientResponse.Stream(of: String.self, error: error) - - XCTAssertEqual(response.metadata, [:]) - await XCTAssertThrowsRPCErrorAsync { - try await response.messages.collect() - } errorHandler: { - XCTAssertEqual($0, error) - } - } - - func testStreamToSingleConversionForValidStream() async throws { - let stream = ClientResponse.Stream( - of: String.self, - metadata: ["foo": "bar"], - bodyParts: .elements(.message("foo"), .trailingMetadata(["bar": "baz"])) - ) - - let single = await ClientResponse.Single(stream: stream) - XCTAssertEqual(single.metadata, ["foo": "bar"]) - XCTAssertEqual(try single.message, "foo") - XCTAssertEqual(single.trailingMetadata, ["bar": "baz"]) - } - - func testStreamToSingleConversionForFailedStream() async throws { - let error = RPCError(code: .aborted, message: "aborted", metadata: ["bar": "baz"]) - let stream = ClientResponse.Stream(of: String.self, error: error) - - let single = await ClientResponse.Single(stream: stream) - XCTAssertEqual(single.metadata, [:]) - XCTAssertThrowsRPCError(try single.message) { - XCTAssertEqual($0, error) - } - XCTAssertEqual(single.trailingMetadata, ["bar": "baz"]) - } - - func testStreamToSingleConversionForInvalidSingleStream() async throws { - let bodies: [[ClientResponse.Stream.Contents.BodyPart]] = [ - [.message("1"), .message("2")], // Too many messages. - [.trailingMetadata([:])], // Too few messages - ] - - for body in bodies { - let stream = ClientResponse.Stream( - of: String.self, - metadata: ["foo": "bar"], - bodyParts: .elements(body) - ) - - let single = await ClientResponse.Single(stream: stream) - XCTAssertEqual(single.metadata, [:]) - XCTAssertThrowsRPCError(try single.message) { error in - XCTAssertEqual(error.code, .unimplemented) - } - XCTAssertEqual(single.trailingMetadata, [:]) - } - } - - func testStreamToSingleConversionForInvalidStream() async throws { - let bodies: [[ClientResponse.Stream.Contents.BodyPart]] = [ - [], // Empty stream - [.trailingMetadata([:]), .trailingMetadata([:])], // Multiple metadatas - [.trailingMetadata([:]), .message("")], // Metadata then message - ] - - for body in bodies { - let stream = ClientResponse.Stream( - of: String.self, - metadata: ["foo": "bar"], - bodyParts: .elements(body) - ) - - let single = await ClientResponse.Single(stream: stream) - XCTAssertEqual(single.metadata, [:]) - XCTAssertThrowsRPCError(try single.message) { error in - XCTAssertEqual(error.code, .internalError) - } - XCTAssertEqual(single.trailingMetadata, [:]) - } - } - - func testStreamToSingleConversionForStreamThrowingRPCError() async throws { - let error = RPCError(code: .dataLoss, message: "oops") - let stream = ClientResponse.Stream( - of: String.self, - metadata: [:], - bodyParts: .throwing(error) - ) - - let single = await ClientResponse.Single(stream: stream) - XCTAssertThrowsRPCError(try single.message) { - XCTAssertEqual($0, error) - } - } - - func testStreamToSingleConversionForStreamThrowingUnknownError() async throws { - let stream = ClientResponse.Stream( - of: String.self, - metadata: [:], - bodyParts: .throwing(CancellationError()) - ) - - let single = await ClientResponse.Single(stream: stream) - XCTAssertThrowsRPCError(try single.message) { error in - XCTAssertEqual(error.code, .unknown) - } - } -} diff --git a/Tests/GRPCCoreTests/Call/Client/Internal/ClientRPCExecutorTestSupport/ClientRPCExecutorTestHarness+ServerBehavior.swift b/Tests/GRPCCoreTests/Call/Client/Internal/ClientRPCExecutorTestSupport/ClientRPCExecutorTestHarness+ServerBehavior.swift deleted file mode 100644 index 0c2ab936f..000000000 --- a/Tests/GRPCCoreTests/Call/Client/Internal/ClientRPCExecutorTestSupport/ClientRPCExecutorTestHarness+ServerBehavior.swift +++ /dev/null @@ -1,121 +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. - */ - -import XCTest - -@testable import GRPCCore - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension ClientRPCExecutorTestHarness { - struct ServerStreamHandler: Sendable { - private let handler: - @Sendable ( - _ stream: RPCStream< - RPCAsyncSequence, - RPCWriter.Closable - > - ) async throws -> Void - - init( - _ handler: @escaping @Sendable ( - RPCStream< - RPCAsyncSequence, - RPCWriter.Closable - > - ) async throws -> Void - ) { - self.handler = handler - } - - func handle( - stream: RPCStream - ) async throws - where - Inbound.Element == RPCRequestPart, - Inbound.Failure == any Error, - Outbound.Element == RPCResponsePart - { - let erased = RPCStream( - descriptor: stream.descriptor, - inbound: RPCAsyncSequence(wrapping: stream.inbound), - outbound: RPCWriter.Closable(wrapping: stream.outbound) - ) - - try await self.handler(erased) - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension ClientRPCExecutorTestHarness.ServerStreamHandler { - static var echo: Self { - return Self { stream in - let response = stream.inbound.map { part -> RPCResponsePart in - switch part { - case .metadata(let metadata): - return .metadata(metadata) - case .message(let bytes): - return .message(bytes) - } - } - - try await stream.outbound.write(contentsOf: response) - try await stream.outbound.write(.status(Status(code: .ok, message: ""), [:])) - await stream.outbound.finish() - } - } - - static func reject( - withError error: RPCError, - consumeInbound: Bool = false - ) -> Self { - return Self { stream in - if consumeInbound { - for try await _ in stream.inbound {} - } - - // All error codes are valid status codes, '!' is safe. - let status = Status(code: Status.Code(error.code), message: error.message) - try await stream.outbound.write(.status(status, error.metadata)) - await stream.outbound.finish() - } - } - - static var failTest: Self { - return Self { stream in - XCTFail("Server accepted unexpected stream") - let status = Status(code: .unknown, message: "Unexpected stream") - try await stream.outbound.write(.status(status, [:])) - await stream.outbound.finish() - } - } - - static func attemptBased(_ onAttempt: @Sendable @escaping (_ attempt: Int) -> Self) -> Self { - let attempts = AtomicCounter(1) - return Self { stream in - let (oldAttemptCount, _) = attempts.increment() - let handler = onAttempt(oldAttemptCount) - try await handler.handle(stream: stream) - } - } - - static func sleepFor(duration: Duration, then handler: Self) -> Self { - return Self { stream in - try await Task.sleep(until: .now.advanced(by: duration), clock: .continuous) - try await handler.handle(stream: stream) - } - } -} diff --git a/Tests/GRPCCoreTests/Call/Client/Internal/ClientRPCExecutorTestSupport/ClientRPCExecutorTestHarness+Transport.swift b/Tests/GRPCCoreTests/Call/Client/Internal/ClientRPCExecutorTestSupport/ClientRPCExecutorTestHarness+Transport.swift deleted file mode 100644 index 0500b23ec..000000000 --- a/Tests/GRPCCoreTests/Call/Client/Internal/ClientRPCExecutorTestSupport/ClientRPCExecutorTestHarness+Transport.swift +++ /dev/null @@ -1,27 +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. - */ - -import GRPCCore -import GRPCInProcessTransport - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension InProcessServerTransport { - func spawnClientTransport( - throttle: RetryThrottle = RetryThrottle(maxTokens: 10, tokenRatio: 0.1) - ) -> InProcessClientTransport { - return InProcessClientTransport(server: self) - } -} diff --git a/Tests/GRPCCoreTests/Call/Client/Internal/ClientRPCExecutorTestSupport/ClientRPCExecutorTestHarness.swift b/Tests/GRPCCoreTests/Call/Client/Internal/ClientRPCExecutorTestSupport/ClientRPCExecutorTestHarness.swift deleted file mode 100644 index 06f200ae6..000000000 --- a/Tests/GRPCCoreTests/Call/Client/Internal/ClientRPCExecutorTestSupport/ClientRPCExecutorTestHarness.swift +++ /dev/null @@ -1,151 +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. - */ - -import GRPCInProcessTransport -import XCTest - -@testable import GRPCCore - -/// A test harness for the ``ClientRPCExecutor``. -/// -/// It provides different hooks for controlling the transport implementation and the behaviour -/// of the server to allow for flexible testing scenarios with minimal boilerplate. The harness -/// also tracks how many streams the client has opened, how many streams the server accepted, and -/// how many streams the client failed to open. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -struct ClientRPCExecutorTestHarness { - private let server: ServerStreamHandler - private let clientTransport: StreamCountingClientTransport - private let serverTransport: StreamCountingServerTransport - - var clientStreamsOpened: Int { - self.clientTransport.streamsOpened - } - - var clientStreamOpenFailures: Int { - self.clientTransport.streamFailures - } - - var serverStreamsAccepted: Int { - self.serverTransport.acceptedStreamsCount - } - - init(transport: Transport = .inProcess, server: ServerStreamHandler) { - self.server = server - - switch transport { - case .inProcess: - let server = InProcessServerTransport() - let client = server.spawnClientTransport() - self.serverTransport = StreamCountingServerTransport(wrapping: server) - self.clientTransport = StreamCountingClientTransport(wrapping: client) - - case .throwsOnStreamCreation(let code): - let server = InProcessServerTransport() // Will never be called. - let client = ThrowOnStreamCreationTransport(code: code) - self.serverTransport = StreamCountingServerTransport(wrapping: server) - self.clientTransport = StreamCountingClientTransport(wrapping: client) - } - } - - enum Transport { - case inProcess - case throwsOnStreamCreation(code: RPCError.Code) - } - - func unary( - request: ClientRequest.Single<[UInt8]>, - options: CallOptions = .defaults, - handler: @escaping @Sendable (ClientResponse.Single<[UInt8]>) async throws -> Void - ) async throws { - try await self.bidirectional(request: ClientRequest.Stream(single: request), options: options) { - response in - try await handler(ClientResponse.Single(stream: response)) - } - } - - func clientStreaming( - request: ClientRequest.Stream<[UInt8]>, - options: CallOptions = .defaults, - handler: @escaping @Sendable (ClientResponse.Single<[UInt8]>) async throws -> Void - ) async throws { - try await self.bidirectional(request: request, options: options) { response in - try await handler(ClientResponse.Single(stream: response)) - } - } - - func serverStreaming( - request: ClientRequest.Single<[UInt8]>, - options: CallOptions = .defaults, - handler: @escaping @Sendable (ClientResponse.Stream<[UInt8]>) async throws -> Void - ) async throws { - try await self.bidirectional(request: ClientRequest.Stream(single: request), options: options) { - response in - try await handler(response) - } - } - - func bidirectional( - request: ClientRequest.Stream<[UInt8]>, - options: CallOptions = .defaults, - handler: @escaping @Sendable (ClientResponse.Stream<[UInt8]>) async throws -> Void - ) async throws { - try await self.execute( - request: request, - serializer: IdentitySerializer(), - deserializer: IdentityDeserializer(), - options: options, - handler: handler - ) - } - - private func execute( - request: ClientRequest.Stream, - serializer: some MessageSerializer, - deserializer: some MessageDeserializer, - options: CallOptions, - handler: @escaping @Sendable (ClientResponse.Stream) async throws -> Void - ) async throws { - try await withThrowingTaskGroup(of: Void.self) { group in - group.addTask { - try await self.serverTransport.listen { stream, context in - try? await self.server.handle(stream: stream) - } - } - - group.addTask { - try await self.clientTransport.connect() - } - - // Execute the request. - try await ClientRPCExecutor.execute( - request: request, - method: MethodDescriptor(service: "foo", method: "bar"), - options: options, - serializer: serializer, - deserializer: deserializer, - transport: self.clientTransport, - interceptors: [], - handler: handler - ) - - // Close the client so the server can finish. - self.clientTransport.beginGracefulShutdown() - self.serverTransport.beginGracefulShutdown() - group.cancelAll() - } - } -} diff --git a/Tests/GRPCCoreTests/Call/Client/Internal/ClientRPCExecutorTests+Hedging.swift b/Tests/GRPCCoreTests/Call/Client/Internal/ClientRPCExecutorTests+Hedging.swift deleted file mode 100644 index 6dceb9976..000000000 --- a/Tests/GRPCCoreTests/Call/Client/Internal/ClientRPCExecutorTests+Hedging.swift +++ /dev/null @@ -1,207 +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. - */ -import GRPCCore -import XCTest - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension ClientRPCExecutorTests { - func testHedgingWhenAllAttemptsResultInNonFatalCodes() async throws { - let harness = ClientRPCExecutorTestHarness( - server: .reject(withError: RPCError(code: .unavailable, message: "")) - ) - - try await harness.bidirectional( - request: ClientRequest.Stream { - try await $0.write([0]) - try await $0.write([1]) - try await $0.write([2]) - }, - options: .hedge(nonFatalCodes: [.unavailable]) - ) { response in - XCTAssertRejected(response) { error in - XCTAssertEqual(error.code, .unavailable) - XCTAssertEqual(Array(error.metadata[stringValues: "grpc-previous-rpc-attempts"]), ["4"]) - } - } - - // All five attempts fail. - XCTAssertEqual(harness.clientStreamsOpened, 5) - XCTAssertEqual(harness.serverStreamsAccepted, 5) - } - - func testHedgingRespectsFatalStatusCodes() async throws { - let harness = ClientRPCExecutorTestHarness( - server: .reject(withError: RPCError(code: .aborted, message: "")) - ) - - try await harness.bidirectional( - request: ClientRequest.Stream { - try await $0.write([0]) - try await $0.write([1]) - try await $0.write([2]) - }, - // Set a long delay to reduce the risk of racing the second attempt and checking the number - // of streams being opened. - options: .hedge(delay: .seconds(5), nonFatalCodes: []) - ) { response in - XCTAssertRejected(response) { error in - XCTAssertEqual(error.code, .aborted) - } - } - - // The first response is fatal. - XCTAssertEqual(harness.clientStreamsOpened, 1) - XCTAssertEqual(harness.serverStreamsAccepted, 1) - - } - - func testHedgingWhenServerIsSlowToRespond() async throws { - let harness = ClientRPCExecutorTestHarness( - server: .attemptBased { attempt in - if attempt == 5 { - return .echo - } else { - return .sleepFor( - duration: .seconds(60), - then: .reject(withError: RPCError(code: .unavailable, message: "")) - ) - } - } - ) - - let start = ContinuousClock.now - try await harness.bidirectional( - request: ClientRequest.Stream { - try await $0.write([0]) - try await $0.write([1]) - try await $0.write([2]) - }, - options: .hedge( - maxAttempts: 5, - delay: .milliseconds(10), - nonFatalCodes: [.unavailable] - ) - ) { response in - let duration = ContinuousClock.now - start - // Should take significantly less than the 60 seconds of the slow responders to get a - // response from the fast responder. Use a large amount of leeway to avoid false positives - // in slow CI systems. - XCTAssertLessThanOrEqual(duration, .milliseconds(500)) - - let messages = try await response.messages.collect() - XCTAssertEqual(messages, [[0], [1], [2]]) - XCTAssertEqual(Array(response.metadata[stringValues: "grpc-previous-rpc-attempts"]), ["4"]) - } - - // Only the 5th attempt succeeds. - XCTAssertEqual(harness.clientStreamsOpened, 5) - XCTAssertEqual(harness.serverStreamsAccepted, 5) - } - - func testHedgingWithServerPushback() async throws { - let harness = ClientRPCExecutorTestHarness( - server: .attemptBased { attempt in - if attempt == 2 { - return .echo - } else { - return .init { stream in - let status = Status(code: .unavailable, message: "") - let metadata: Metadata = ["grpc-retry-delay-ms": "10"] - try await stream.outbound.write(.status(status, metadata)) - } - } - } - ) - - let start = ContinuousClock.now - try await harness.bidirectional( - request: ClientRequest.Stream { - try await $0.write([0]) - try await $0.write([1]) - try await $0.write([2]) - }, - options: .hedge( - maxAttempts: 5, - delay: .seconds(60), // High delay, server pushback will override this. - nonFatalCodes: [.unavailable] - ) - ) { response in - let duration = ContinuousClock.now - start - // Should take significantly less than the 60 seconds. The server pushback is only 10 ms which - // should override the configured delay. Use a large amount of leeway to avoid false positives - // in slow CI systems. - XCTAssertLessThanOrEqual(duration, .milliseconds(500)) - - let messages = try await response.messages.collect() - XCTAssertEqual(messages, [[0], [1], [2]]) - XCTAssertEqual(Array(response.metadata[stringValues: "grpc-previous-rpc-attempts"]), ["1"]) - } - - // Only the 2nd attempt succeeds. - XCTAssertEqual(harness.clientStreamsOpened, 2) - XCTAssertEqual(harness.serverStreamsAccepted, 2) - } - - func testHedgingWithNegativeServerPushback() async throws { - // Negative and values which can't be parsed should halt retries. - for pushback in ["-1", "not-an-int"] { - let harness = ClientRPCExecutorTestHarness( - server: .reject( - withError: RPCError( - code: .unavailable, - message: "", - metadata: ["grpc-retry-pushback-ms": "\(pushback)"] - ) - ) - ) - - try await harness.bidirectional( - request: ClientRequest.Stream { - try await $0.write([0]) - }, - options: .hedge(delay: .seconds(60), nonFatalCodes: [.unavailable]) - ) { response in - XCTAssertRejected(response) { error in - XCTAssertEqual(error.code, .unavailable) - } - } - - // Only one attempt should be made. - XCTAssertEqual(harness.clientStreamsOpened, 1) - XCTAssertEqual(harness.serverStreamsAccepted, 1) - } - } -} - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -extension CallOptions { - fileprivate static func hedge( - maxAttempts: Int = 5, - delay: Duration = .milliseconds(25), - nonFatalCodes: Set, - timeout: Duration? = nil - ) -> Self { - let policy = HedgingPolicy( - maxAttempts: maxAttempts, - hedgingDelay: delay, - nonFatalStatusCodes: nonFatalCodes - ) - - var options = CallOptions.defaults - options.executionPolicy = .hedge(policy) - return options - } -} diff --git a/Tests/GRPCCoreTests/Call/Client/Internal/ClientRPCExecutorTests+Retries.swift b/Tests/GRPCCoreTests/Call/Client/Internal/ClientRPCExecutorTests+Retries.swift deleted file mode 100644 index 6c2e6cd1e..000000000 --- a/Tests/GRPCCoreTests/Call/Client/Internal/ClientRPCExecutorTests+Retries.swift +++ /dev/null @@ -1,320 +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. - */ -import GRPCCore -import XCTest - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension ClientRPCExecutorTests { - fileprivate func makeHarnessForRetries( - rejectUntilAttempt firstSuccessfulAttempt: Int, - withCode code: RPCError.Code, - consumeInboundStream: Bool = false - ) -> ClientRPCExecutorTestHarness { - return ClientRPCExecutorTestHarness( - server: .attemptBased { attempt in - guard attempt < firstSuccessfulAttempt else { - return .echo - } - - return .reject( - withError: RPCError(code: code, message: ""), - consumeInbound: consumeInboundStream - ) - } - ) - } - - func testRetriesEventuallySucceed() async throws { - let harness = self.makeHarnessForRetries( - rejectUntilAttempt: 3, - withCode: .unavailable, - consumeInboundStream: true - ) - try await harness.bidirectional( - request: ClientRequest.Stream(metadata: ["foo": "bar"]) { - try await $0.write([0]) - try await $0.write([1]) - try await $0.write([2]) - }, - options: .retry(codes: [.unavailable]) - ) { response in - XCTAssertEqual( - response.metadata, - [ - "foo": "bar", - "grpc-previous-rpc-attempts": "2", - ] - ) - let messages = try await response.messages.collect() - XCTAssertEqual(messages, [[0], [1], [2]]) - } - - // Success on the third attempt. - XCTAssertEqual(harness.clientStreamsOpened, 3) - XCTAssertEqual(harness.serverStreamsAccepted, 3) - } - - func testRetriesRespectRetryableCodes() async throws { - let harness = self.makeHarnessForRetries(rejectUntilAttempt: 3, withCode: .unavailable) - try await harness.bidirectional( - request: ClientRequest.Stream(metadata: ["foo": "bar"]) { - try await $0.write([0, 1, 2]) - }, - options: .retry(codes: [.aborted]) - ) { response in - switch response.accepted { - case .success: - XCTFail("Expected response to be rejected") - case .failure(let error): - XCTAssertEqual(error.code, .unavailable) - } - } - - // Error code wasn't retryable, only one stream. - XCTAssertEqual(harness.clientStreamsOpened, 1) - XCTAssertEqual(harness.serverStreamsAccepted, 1) - } - - func testRetriesRespectRetryLimit() async throws { - let harness = self.makeHarnessForRetries(rejectUntilAttempt: 5, withCode: .unavailable) - try await harness.bidirectional( - request: ClientRequest.Stream(metadata: ["foo": "bar"]) { - try await $0.write([0, 1, 2]) - }, - options: .retry(maximumAttempts: 2, codes: [.unavailable]) - ) { response in - switch response.accepted { - case .success: - XCTFail("Expected response to be rejected") - case .failure(let error): - XCTAssertEqual(error.code, .unavailable) - XCTAssertEqual(Array(error.metadata[stringValues: "grpc-previous-rpc-attempts"]), ["1"]) - } - } - - // Only two attempts permitted. - XCTAssertEqual(harness.clientStreamsOpened, 2) - XCTAssertEqual(harness.serverStreamsAccepted, 2) - } - - func testRetriesCantBeExecutedForTooManyRequestMessages() async throws { - let harness = self.makeHarnessForRetries( - rejectUntilAttempt: 3, - withCode: .unavailable, - consumeInboundStream: true - ) - - try await harness.bidirectional( - request: ClientRequest.Stream { - for _ in 0 ..< 1000 { - try await $0.write([]) - } - }, - options: .retry(codes: [.unavailable]) - ) { response in - switch response.accepted { - case .success: - XCTFail("Expected response to be rejected") - case .failure(let error): - XCTAssertEqual(error.code, .unavailable) - XCTAssertFalse(error.metadata.contains { $0.key == "grpc-previous-rpc-attempts" }) - } - } - - // The request stream can't be buffered as it's a) large, and b) the server consumes it before - // responding. Even though the server responded with a retryable status code, the request buffer - // was dropped so only one attempt was made. - XCTAssertEqual(harness.clientStreamsOpened, 1) - XCTAssertEqual(harness.serverStreamsAccepted, 1) - } - - func testRetriesWithImmediateTimeout() async throws { - let harness = ClientRPCExecutorTestHarness( - server: .sleepFor(duration: .milliseconds(250), then: .echo) - ) - - await XCTAssertThrowsErrorAsync { - try await harness.bidirectional( - request: ClientRequest.Stream { - try await $0.write([0]) - try await $0.write([1]) - try await $0.write([2]) - }, - options: .retry(codes: [.unavailable], timeout: .zero) - ) { response in - XCTFail("Response not expected to be handled") - } - } errorHandler: { error in - XCTAssert(error is CancellationError) - } - } - - func testRetriesWithTimeoutDuringFirstAttempt() async throws { - let harness = ClientRPCExecutorTestHarness( - server: .sleepFor(duration: .milliseconds(250), then: .echo) - ) - - await XCTAssertThrowsErrorAsync { - try await harness.bidirectional( - request: ClientRequest.Stream { - try await $0.write([0]) - try await $0.write([1]) - try await $0.write([2]) - }, - options: .retry(codes: [.unavailable], timeout: .milliseconds(50)) - ) { response in - XCTFail("Response not expected to be handled") - } - } errorHandler: { error in - XCTAssert(error is CancellationError) - } - } - - func testRetriesWithTimeoutDuringSecondAttempt() async throws { - let harness = ClientRPCExecutorTestHarness( - server: .sleepFor( - duration: .milliseconds(100), - then: .reject(withError: RPCError(code: .unavailable, message: "")) - ) - ) - - await XCTAssertThrowsErrorAsync { - try await harness.bidirectional( - request: ClientRequest.Stream { - try await $0.write([0]) - try await $0.write([1]) - try await $0.write([2]) - }, - options: .retry(codes: [.unavailable], timeout: .milliseconds(150)) - ) { response in - XCTFail("Response not expected to be handled") - } - } errorHandler: { error in - XCTAssert(error is CancellationError) - } - } - - func testRetriesWithServerPushback() async throws { - let harness = ClientRPCExecutorTestHarness( - server: .attemptBased { attempt in - if attempt == 2 { - return .echo - } else { - return .init { stream in - // Use a short pushback to override the long configured retry delay. - let status = Status(code: .unavailable, message: "") - let metadata: Metadata = ["grpc-retry-pushback-ms": "10"] - try await stream.outbound.write(.status(status, metadata)) - } - } - } - ) - - let retryPolicy = RetryPolicy( - maxAttempts: 5, - initialBackoff: .seconds(60), - maxBackoff: .seconds(50), - backoffMultiplier: 1, - retryableStatusCodes: [.unavailable] - ) - - let start = ContinuousClock.now - try await harness.bidirectional( - request: ClientRequest.Stream { - try await $0.write([0]) - }, - options: .retry(retryPolicy) - ) { response in - let end = ContinuousClock.now - let duration = end - start - // Loosely check whether the RPC completed in less than 60 seconds (i.e. the configured retry - // delay). Allow lots of headroom to avoid false negatives; CI systems can be slow. - XCTAssertLessThanOrEqual(duration, .seconds(5)) - XCTAssertEqual(Array(response.metadata[stringValues: "grpc-previous-rpc-attempts"]), ["1"]) - } - } - - func testRetriesWithNegativeServerPushback() async throws { - // Negative and values which can't be parsed should halt retries. - for pushback in ["-1", "not-an-int"] { - let harness = ClientRPCExecutorTestHarness( - server: .reject( - withError: RPCError( - code: .unavailable, - message: "", - metadata: ["grpc-retry-pushback-ms": "\(pushback)"] - ) - ) - ) - - let retryPolicy = RetryPolicy( - maxAttempts: 5, - initialBackoff: .seconds(60), - maxBackoff: .seconds(50), - backoffMultiplier: 1, - retryableStatusCodes: [.unavailable] - ) - - try await harness.bidirectional( - request: ClientRequest.Stream { - try await $0.write([0]) - }, - options: .retry(retryPolicy) - ) { response in - switch response.accepted { - case .success: - XCTFail("Expected RPC to fail") - case .failure(let error): - XCTAssertEqual(error.code, .unavailable) - } - } - - // Only one attempt should be made. - XCTAssertEqual(harness.clientStreamsOpened, 1) - XCTAssertEqual(harness.serverStreamsAccepted, 1) - } - } -} - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -extension CallOptions { - fileprivate static func retry( - _ policy: RetryPolicy - ) -> Self { - var options: CallOptions = .defaults - options.executionPolicy = .retry(policy) - return options - } - - fileprivate static func retry( - maximumAttempts: Int = 5, - codes: Set, - timeout: Duration? = nil - ) -> Self { - let policy = RetryPolicy( - maxAttempts: maximumAttempts, - initialBackoff: .milliseconds(10), - maxBackoff: .milliseconds(100), - backoffMultiplier: 1.6, - retryableStatusCodes: codes - ) - - var options: CallOptions = .defaults - options.executionPolicy = .retry(policy) - options.timeout = timeout - return options - } -} diff --git a/Tests/GRPCCoreTests/Call/Client/Internal/ClientRPCExecutorTests.swift b/Tests/GRPCCoreTests/Call/Client/Internal/ClientRPCExecutorTests.swift deleted file mode 100644 index c2396fdef..000000000 --- a/Tests/GRPCCoreTests/Call/Client/Internal/ClientRPCExecutorTests.swift +++ /dev/null @@ -1,272 +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. - */ - -import XCTest - -@testable import GRPCCore - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -final class ClientRPCExecutorTests: XCTestCase { - func testUnaryEcho() async throws { - let tester = ClientRPCExecutorTestHarness(server: .echo) - try await tester.unary( - request: ClientRequest.Single(message: [1, 2, 3], metadata: ["foo": "bar"]) - ) { response in - XCTAssertEqual(response.metadata, ["foo": "bar"]) - XCTAssertEqual(try response.message, [1, 2, 3]) - } - - XCTAssertEqual(tester.clientStreamsOpened, 1) - XCTAssertEqual(tester.serverStreamsAccepted, 1) - } - - func testClientStreamingEcho() async throws { - let tester = ClientRPCExecutorTestHarness(server: .echo) - try await tester.clientStreaming( - request: ClientRequest.Stream(metadata: ["foo": "bar"]) { - try await $0.write([1, 2, 3]) - } - ) { response in - XCTAssertEqual(response.metadata, ["foo": "bar"]) - XCTAssertEqual(try response.message, [1, 2, 3]) - } - - XCTAssertEqual(tester.clientStreamsOpened, 1) - XCTAssertEqual(tester.serverStreamsAccepted, 1) - } - - func testServerStreamingEcho() async throws { - let tester = ClientRPCExecutorTestHarness(server: .echo) - try await tester.serverStreaming( - request: ClientRequest.Single(message: [1, 2, 3], metadata: ["foo": "bar"]) - ) { response in - XCTAssertEqual(response.metadata, ["foo": "bar"]) - let messages = try await response.messages.collect() - XCTAssertEqual(messages, [[1, 2, 3]]) - } - - XCTAssertEqual(tester.clientStreamsOpened, 1) - XCTAssertEqual(tester.serverStreamsAccepted, 1) - } - - func testBidirectionalStreamingEcho() async throws { - let tester = ClientRPCExecutorTestHarness(server: .echo) - try await tester.bidirectional( - request: ClientRequest.Stream(metadata: ["foo": "bar"]) { - try await $0.write([1, 2, 3]) - } - ) { response in - XCTAssertEqual(response.metadata, ["foo": "bar"]) - let messages = try await response.messages.collect() - XCTAssertEqual(messages, [[1, 2, 3]]) - } - - XCTAssertEqual(tester.clientStreamsOpened, 1) - XCTAssertEqual(tester.serverStreamsAccepted, 1) - } - - func testUnaryRejectedByServer() async throws { - let error = RPCError(code: .unauthenticated, message: "", metadata: ["metadata": "error"]) - let tester = ClientRPCExecutorTestHarness(server: .reject(withError: error)) - try await tester.unary( - request: ClientRequest.Single(message: [1, 2, 3], metadata: ["foo": "bar"]) - ) { response in - XCTAssertThrowsRPCError(try response.message) { - XCTAssertEqual($0, error) - } - } - - XCTAssertEqual(tester.clientStreamsOpened, 1) - XCTAssertEqual(tester.serverStreamsAccepted, 1) - } - - func testClientStreamingRejectedByServer() async throws { - let error = RPCError(code: .unauthenticated, message: "", metadata: ["metadata": "error"]) - let tester = ClientRPCExecutorTestHarness(server: .reject(withError: error)) - try await tester.clientStreaming( - request: ClientRequest.Stream(metadata: ["foo": "bar"]) { - try await $0.write([1, 2, 3]) - } - ) { response in - XCTAssertThrowsRPCError(try response.message) { - XCTAssertEqual($0, error) - } - } - - XCTAssertEqual(tester.clientStreamsOpened, 1) - XCTAssertEqual(tester.serverStreamsAccepted, 1) - } - - func testServerStreamingRejectedByServer() async throws { - let error = RPCError(code: .unauthenticated, message: "", metadata: ["metadata": "error"]) - let tester = ClientRPCExecutorTestHarness(server: .reject(withError: error)) - try await tester.serverStreaming( - request: ClientRequest.Single(message: [1, 2, 3], metadata: ["foo": "bar"]) - ) { response in - await XCTAssertThrowsRPCErrorAsync { - try await response.messages.collect() - } errorHandler: { - XCTAssertEqual($0, error) - } - } - - XCTAssertEqual(tester.clientStreamsOpened, 1) - XCTAssertEqual(tester.serverStreamsAccepted, 1) - } - - func testBidirectionalRejectedByServer() async throws { - let error = RPCError(code: .unauthenticated, message: "", metadata: ["metadata": "error"]) - let tester = ClientRPCExecutorTestHarness(server: .reject(withError: error)) - try await tester.bidirectional( - request: ClientRequest.Stream(metadata: ["foo": "bar"]) { - try await $0.write([1, 2, 3]) - } - ) { response in - await XCTAssertThrowsRPCErrorAsync { - try await response.messages.collect() - } errorHandler: { - XCTAssertEqual($0, error) - } - } - - XCTAssertEqual(tester.clientStreamsOpened, 1) - XCTAssertEqual(tester.serverStreamsAccepted, 1) - } - - func testUnaryUnableToOpenStream() async throws { - let tester = ClientRPCExecutorTestHarness( - transport: .throwsOnStreamCreation(code: .aborted), - server: .failTest - ) - - await XCTAssertThrowsRPCErrorAsync { - try await tester.unary( - request: ClientRequest.Single(message: [1, 2, 3], metadata: ["foo": "bar"]) - ) { _ in } - } errorHandler: { error in - XCTAssertEqual(error.code, .aborted) - } - - XCTAssertEqual(tester.clientStreamsOpened, 0) - XCTAssertEqual(tester.clientStreamOpenFailures, 1) - XCTAssertEqual(tester.serverStreamsAccepted, 0) - } - - func testClientStreamingUnableToOpenStream() async throws { - let tester = ClientRPCExecutorTestHarness( - transport: .throwsOnStreamCreation(code: .aborted), - server: .failTest - ) - - await XCTAssertThrowsRPCErrorAsync { - try await tester.clientStreaming( - request: ClientRequest.Stream(metadata: ["foo": "bar"]) { - try await $0.write([1, 2, 3]) - } - ) { _ in } - } errorHandler: { error in - XCTAssertEqual(error.code, .aborted) - } - - XCTAssertEqual(tester.clientStreamsOpened, 0) - XCTAssertEqual(tester.clientStreamOpenFailures, 1) - XCTAssertEqual(tester.serverStreamsAccepted, 0) - } - - func testServerStreamingUnableToOpenStream() async throws { - let tester = ClientRPCExecutorTestHarness( - transport: .throwsOnStreamCreation(code: .aborted), - server: .failTest - ) - - await XCTAssertThrowsRPCErrorAsync { - try await tester.serverStreaming( - request: ClientRequest.Single(message: [1, 2, 3], metadata: ["foo": "bar"]) - ) { _ in } - } errorHandler: { - XCTAssertEqual($0.code, .aborted) - } - - XCTAssertEqual(tester.clientStreamsOpened, 0) - XCTAssertEqual(tester.clientStreamOpenFailures, 1) - XCTAssertEqual(tester.serverStreamsAccepted, 0) - } - - func testBidirectionalUnableToOpenStream() async throws { - let tester = ClientRPCExecutorTestHarness( - transport: .throwsOnStreamCreation(code: .aborted), - server: .failTest - ) - - await XCTAssertThrowsRPCErrorAsync { - try await tester.bidirectional( - request: ClientRequest.Stream(metadata: ["foo": "bar"]) { - try await $0.write([1, 2, 3]) - } - ) { _ in } - } errorHandler: { - XCTAssertEqual($0.code, .aborted) - } - - XCTAssertEqual(tester.clientStreamsOpened, 0) - XCTAssertEqual(tester.clientStreamOpenFailures, 1) - XCTAssertEqual(tester.serverStreamsAccepted, 0) - } - - func testTimeoutIsPropagated() async throws { - // 'nil' means no retires or hedging, just try to execute the RPC once. - var policies: [RPCExecutionPolicy?] = [nil] - - let retryPolicy = RetryPolicy( - maxAttempts: 5, - initialBackoff: .seconds(1), - maxBackoff: .seconds(1), - backoffMultiplier: 1.6, - retryableStatusCodes: [.unavailable] - ) - policies.append(.retry(retryPolicy)) - - let hedgingPolicy = HedgingPolicy( - maxAttempts: 5, - hedgingDelay: .seconds(1), - nonFatalStatusCodes: [.unavailable] - ) - policies.append(.hedge(hedgingPolicy)) - - for policy in policies { - let timeout = Duration.seconds(120) - var options = CallOptions.defaults - options.timeout = timeout - options.executionPolicy = policy - - let tester = ClientRPCExecutorTestHarness(transport: .inProcess, server: .echo) - try await tester.unary( - request: ClientRequest.Single(message: []), - options: options - ) { response in - let timeoutMetadata = Array(response.metadata[stringValues: "grpc-timeout"]) - let parsed = try XCTUnwrap(timeoutMetadata.first.flatMap { Timeout(decoding: $0) }) - - // The timeout is handled as a deadline internally and gets converted back to a timeout - // when transmitted as metadata, so allow some leeway when checking the value. - let leeway = Duration.seconds(1) - let acceptable: ClosedRange = timeout - leeway ... timeout + leeway - - XCTAssert(acceptable.contains(parsed.duration)) - } - } - } -} diff --git a/Tests/GRPCCoreTests/Call/Client/RetryDelaySequenceTests.swift b/Tests/GRPCCoreTests/Call/Client/RetryDelaySequenceTests.swift deleted file mode 100644 index cbe0d7a09..000000000 --- a/Tests/GRPCCoreTests/Call/Client/RetryDelaySequenceTests.swift +++ /dev/null @@ -1,93 +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. - */ -import XCTest - -@testable import GRPCCore - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -final class RetryDelaySequenceTests: XCTestCase { - func testSequence() { - let policy = RetryPolicy( - maxAttempts: 3, // ignored here - initialBackoff: .seconds(1), - maxBackoff: .seconds(8), - backoffMultiplier: 2.0, - retryableStatusCodes: [.aborted] // ignored here - ) - - let sequence = RetryDelaySequence(policy: policy) - var iterator = sequence.makeIterator() - - // The iterator will never return 'nil', '!' is safe. - XCTAssertLessThanOrEqual(iterator.next()!, .seconds(1)) - XCTAssertLessThanOrEqual(iterator.next()!, .seconds(2)) - XCTAssertLessThanOrEqual(iterator.next()!, .seconds(4)) - XCTAssertLessThanOrEqual(iterator.next()!, .seconds(8)) - XCTAssertLessThanOrEqual(iterator.next()!, .seconds(8)) // Clamped - } - - func testSequenceSupportsMultipleIteration() { - let policy = RetryPolicy( - maxAttempts: 3, // ignored here - initialBackoff: .seconds(1), - maxBackoff: .seconds(8), - backoffMultiplier: 2.0, - retryableStatusCodes: [.aborted] // ignored here - ) - - let sequence = RetryDelaySequence(policy: policy) - for _ in 0 ..< 10 { - var iterator = sequence.makeIterator() - // The iterator will never return 'nil', '!' is safe. - XCTAssertLessThanOrEqual(iterator.next()!, .seconds(1)) - XCTAssertLessThanOrEqual(iterator.next()!, .seconds(2)) - XCTAssertLessThanOrEqual(iterator.next()!, .seconds(4)) - XCTAssertLessThanOrEqual(iterator.next()!, .seconds(8)) - XCTAssertLessThanOrEqual(iterator.next()!, .seconds(8)) // Clamped - } - } - - func testDurationToDouble() { - let testData: [(Duration, Double)] = [ - (.zero, 0.0), - (.seconds(1), 1.0), - (.milliseconds(1500), 1.5), - (.nanoseconds(1_000_000_000), 1.0), - (.nanoseconds(3_141_592_653 as Int64), 3.141592653), - ] - - for (duration, expected) in testData { - XCTAssertEqual(RetryDelaySequence.Iterator._durationToTimeInterval(duration), expected) - } - } - - func testDoubleToDuration() { - let testData: [(Double, Duration)] = [ - (0.0, .zero), - (1.0, .seconds(1)), - (1.5, .milliseconds(1500)), - (1.0, .nanoseconds(1_000_000_000)), - (3.141592653, .nanoseconds(3_141_592_653 as Int64)), - ] - - for (seconds, expected) in testData { - let actual = RetryDelaySequence.Iterator._timeIntervalToDuration(seconds) - XCTAssertEqual(actual.components.seconds, expected.components.seconds) - // We lose some precision in the conversion, that's fine. - XCTAssertEqual(actual.components.attoseconds / 1_000, expected.components.attoseconds / 1_000) - } - } -} diff --git a/Tests/GRPCCoreTests/Call/Server/Internal/ServerRPCExecutorTestSupport/ServerRPCExecutorTestHarness.swift b/Tests/GRPCCoreTests/Call/Server/Internal/ServerRPCExecutorTestSupport/ServerRPCExecutorTestHarness.swift deleted file mode 100644 index 8d7e0a543..000000000 --- a/Tests/GRPCCoreTests/Call/Server/Internal/ServerRPCExecutorTestSupport/ServerRPCExecutorTestHarness.swift +++ /dev/null @@ -1,146 +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. - */ -import XCTest - -@testable import GRPCCore - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -struct ServerRPCExecutorTestHarness { - struct ServerHandler: Sendable { - let fn: @Sendable (ServerRequest.Stream) async throws -> ServerResponse.Stream - - init( - _ fn: @escaping @Sendable ( - ServerRequest.Stream - ) async throws -> ServerResponse.Stream - ) { - self.fn = fn - } - - func handle( - _ request: ServerRequest.Stream - ) async throws -> ServerResponse.Stream { - try await self.fn(request) - } - - static func throwing(_ error: any Error) -> Self { - return Self { _ in throw error } - } - } - - let interceptors: [any ServerInterceptor] - - init(interceptors: [any ServerInterceptor] = []) { - self.interceptors = interceptors - } - - func execute( - deserializer: some MessageDeserializer, - serializer: some MessageSerializer, - handler: @escaping @Sendable ( - ServerRequest.Stream - ) async throws -> ServerResponse.Stream, - producer: @escaping @Sendable ( - RPCWriter.Closable - ) async throws -> Void, - consumer: @escaping @Sendable ( - RPCAsyncSequence - ) async throws -> Void - ) async throws { - try await self.execute( - deserializer: deserializer, - serializer: serializer, - handler: .init(handler), - producer: producer, - consumer: consumer - ) - } - - func execute( - deserializer: some MessageDeserializer, - serializer: some MessageSerializer, - handler: ServerHandler, - producer: @escaping @Sendable ( - RPCWriter.Closable - ) async throws -> Void, - consumer: @escaping @Sendable ( - RPCAsyncSequence - ) async throws -> Void - ) async throws { - let input = GRPCAsyncThrowingStream.makeStream(of: RPCRequestPart.self) - let output = GRPCAsyncThrowingStream.makeStream(of: RPCResponsePart.self) - - try await withThrowingTaskGroup(of: Void.self) { group in - group.addTask { - try await producer(RPCWriter.Closable(wrapping: input.continuation)) - } - - group.addTask { - try await consumer(RPCAsyncSequence(wrapping: output.stream)) - } - - group.addTask { - let context = ServerContext(descriptor: MethodDescriptor(service: "foo", method: "bar")) - await ServerRPCExecutor.execute( - context: context, - stream: RPCStream( - descriptor: context.descriptor, - inbound: RPCAsyncSequence(wrapping: input.stream), - outbound: RPCWriter.Closable(wrapping: output.continuation) - ), - deserializer: deserializer, - serializer: serializer, - interceptors: self.interceptors, - handler: { stream, context in - try await handler.handle(stream) - } - ) - } - - try await group.waitForAll() - } - } - - func execute( - handler: ServerHandler<[UInt8], [UInt8]> = .echo, - producer: @escaping @Sendable ( - RPCWriter.Closable - ) async throws -> Void, - consumer: @escaping @Sendable ( - RPCAsyncSequence - ) async throws -> Void - ) async throws { - try await self.execute( - deserializer: IdentityDeserializer(), - serializer: IdentitySerializer(), - handler: handler, - producer: producer, - consumer: consumer - ) - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension ServerRPCExecutorTestHarness.ServerHandler where Input == Output { - static var echo: Self { - return Self { request in - return ServerResponse.Stream(metadata: request.metadata) { writer in - try await writer.write(contentsOf: request.messages) - return [:] - } - } - } -} diff --git a/Tests/GRPCCoreTests/Call/Server/Internal/ServerRPCExecutorTests.swift b/Tests/GRPCCoreTests/Call/Server/Internal/ServerRPCExecutorTests.swift deleted file mode 100644 index 5d2aa0029..000000000 --- a/Tests/GRPCCoreTests/Call/Server/Internal/ServerRPCExecutorTests.swift +++ /dev/null @@ -1,354 +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. - */ - -import XCTest - -@testable import GRPCCore - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -final class ServerRPCExecutorTests: XCTestCase { - func testEchoNoMessages() async throws { - let harness = ServerRPCExecutorTestHarness() - try await harness.execute(handler: .echo) { inbound in - try await inbound.write(.metadata(["foo": "bar"])) - await inbound.finish() - } consumer: { outbound in - let parts = try await outbound.collect() - XCTAssertEqual( - parts, - [ - .metadata(["foo": "bar"]), - .status(.ok, [:]), - ] - ) - } - } - - func testEchoSingleMessage() async throws { - let harness = ServerRPCExecutorTestHarness() - try await harness.execute(handler: .echo) { inbound in - try await inbound.write(.metadata(["foo": "bar"])) - try await inbound.write(.message([0])) - await inbound.finish() - } consumer: { outbound in - let parts = try await outbound.collect() - XCTAssertEqual( - parts, - [ - .metadata(["foo": "bar"]), - .message([0]), - .status(.ok, [:]), - ] - ) - } - } - - func testEchoMultipleMessages() async throws { - let harness = ServerRPCExecutorTestHarness() - try await harness.execute(handler: .echo) { inbound in - try await inbound.write(.metadata(["foo": "bar"])) - try await inbound.write(.message([0])) - try await inbound.write(.message([1])) - try await inbound.write(.message([2])) - await inbound.finish() - } consumer: { outbound in - let parts = try await outbound.collect() - XCTAssertEqual( - parts, - [ - .metadata(["foo": "bar"]), - .message([0]), - .message([1]), - .message([2]), - .status(.ok, [:]), - ] - ) - } - } - - func testEchoSingleJSONMessage() async throws { - let harness = ServerRPCExecutorTestHarness() - try await harness.execute( - deserializer: JSONDeserializer(), - serializer: JSONSerializer() - ) { request in - let messages = try await request.messages.collect() - XCTAssertEqual(messages, ["hello"]) - return ServerResponse.Stream(metadata: request.metadata) { writer in - try await writer.write("hello") - return [:] - } - } producer: { inbound in - try await inbound.write(.metadata(["foo": "bar"])) - try await inbound.write(.message(Array("\"hello\"".utf8))) - await inbound.finish() - } consumer: { outbound in - let parts = try await outbound.collect() - XCTAssertEqual( - parts, - [ - .metadata(["foo": "bar"]), - .message(Array("\"hello\"".utf8)), - .status(.ok, [:]), - ] - ) - } - } - - func testEchoMultipleJSONMessages() async throws { - let harness = ServerRPCExecutorTestHarness() - try await harness.execute( - deserializer: JSONDeserializer(), - serializer: JSONSerializer() - ) { request in - let messages = try await request.messages.collect() - XCTAssertEqual(messages, ["hello", "world"]) - return ServerResponse.Stream(metadata: request.metadata) { writer in - try await writer.write("hello") - try await writer.write("world") - return [:] - } - } producer: { inbound in - try await inbound.write(.metadata(["foo": "bar"])) - try await inbound.write(.message(Array("\"hello\"".utf8))) - try await inbound.write(.message(Array("\"world\"".utf8))) - await inbound.finish() - } consumer: { outbound in - let parts = try await outbound.collect() - XCTAssertEqual( - parts, - [ - .metadata(["foo": "bar"]), - .message(Array("\"hello\"".utf8)), - .message(Array("\"world\"".utf8)), - .status(.ok, [:]), - ] - ) - } - } - - func testReturnTrailingMetadata() async throws { - let harness = ServerRPCExecutorTestHarness() - try await harness.execute( - deserializer: IdentityDeserializer(), - serializer: IdentitySerializer() - ) { request in - return ServerResponse.Stream(metadata: request.metadata) { _ in - return ["bar": "baz"] - } - } producer: { inbound in - try await inbound.write(.metadata(["foo": "bar"])) - await inbound.finish() - } consumer: { outbound in - let parts = try await outbound.collect() - XCTAssertEqual( - parts, - [ - .metadata(["foo": "bar"]), - .status(.ok, ["bar": "baz"]), - ] - ) - } - } - - func testEmptyInbound() async throws { - let harness = ServerRPCExecutorTestHarness() - try await harness.execute(handler: .echo) { inbound in - await inbound.finish() - } consumer: { outbound in - let part = try await outbound.collect().first - XCTAssertStatus(part) { status, _ in - XCTAssertEqual(status.code, .internalError) - } - } - } - - func testInboundStreamMissingMetadata() async throws { - let harness = ServerRPCExecutorTestHarness() - try await harness.execute(handler: .echo) { inbound in - try await inbound.write(.message([0])) - await inbound.finish() - } consumer: { outbound in - let part = try await outbound.collect().first - XCTAssertStatus(part) { status, _ in - XCTAssertEqual(status.code, .internalError) - } - } - } - - func testInboundStreamThrows() async throws { - let harness = ServerRPCExecutorTestHarness() - try await harness.execute(handler: .echo) { inbound in - await inbound.finish(throwing: RPCError(code: .aborted, message: "")) - } consumer: { outbound in - let part = try await outbound.collect().first - XCTAssertStatus(part) { status, _ in - XCTAssertEqual(status.code, .unknown) - } - } - } - - func testHandlerThrowsAnyError() async throws { - struct SomeError: Error {} - let harness = ServerRPCExecutorTestHarness() - try await harness.execute(handler: .throwing(SomeError())) { inbound in - try await inbound.write(.metadata([:])) - await inbound.finish() - } consumer: { outbound in - let part = try await outbound.collect().first - XCTAssertStatus(part) { status, _ in - XCTAssertEqual(status.code, .unknown) - } - } - } - - func testHandlerThrowsRPCError() async throws { - let error = RPCError(code: .aborted, message: "RPC aborted", metadata: ["foo": "bar"]) - let harness = ServerRPCExecutorTestHarness() - try await harness.execute(handler: .throwing(error)) { inbound in - try await inbound.write(.metadata([:])) - await inbound.finish() - } consumer: { outbound in - let part = try await outbound.collect().first - XCTAssertStatus(part) { status, metadata in - XCTAssertEqual(status.code, .aborted) - XCTAssertEqual(status.message, "RPC aborted") - XCTAssertEqual(metadata, ["foo": "bar"]) - } - } - } - - func testHandlerRespectsTimeout() async throws { - let harness = ServerRPCExecutorTestHarness() - try await harness.execute( - deserializer: IdentityDeserializer(), - serializer: IdentitySerializer() - ) { request in - do { - try await Task.sleep(until: .now.advanced(by: .seconds(180)), clock: .continuous) - } catch is CancellationError { - throw RPCError(code: .cancelled, message: "Sleep was cancelled") - } - - XCTFail("Server handler should've been cancelled by timeout.") - return ServerResponse.Stream(error: RPCError(code: .failedPrecondition, message: "")) - } producer: { inbound in - try await inbound.write(.metadata(["grpc-timeout": "1000n"])) - await inbound.finish() - } consumer: { outbound in - let part = try await outbound.collect().first - XCTAssertStatus(part) { status, _ in - XCTAssertEqual(status.code, .cancelled) - XCTAssertEqual(status.message, "Sleep was cancelled") - } - } - } - - func testShortCircuitInterceptor() async throws { - let error = RPCError( - code: .unauthenticated, - message: "Unauthenticated", - metadata: ["foo": "bar"] - ) - - // The interceptor skips the handler altogether. - let harness = ServerRPCExecutorTestHarness(interceptors: [.rejectAll(with: error)]) - try await harness.execute( - deserializer: IdentityDeserializer(), - serializer: IdentitySerializer() - ) { request in - XCTFail("Unexpected request") - return ServerResponse.Stream( - of: [UInt8].self, - error: RPCError(code: .failedPrecondition, message: "") - ) - } producer: { inbound in - try await inbound.write(.metadata([:])) - await inbound.finish() - } consumer: { outbound in - let part = try await outbound.collect().first - XCTAssertStatus(part) { status, metadata in - XCTAssertEqual(status.code, .unauthenticated) - XCTAssertEqual(status.message, "Unauthenticated") - XCTAssertEqual(metadata, ["foo": "bar"]) - } - } - } - - func testMultipleInterceptorsAreCalled() async throws { - let counter1 = AtomicCounter() - let counter2 = AtomicCounter() - - // The interceptor skips the handler altogether. - let harness = ServerRPCExecutorTestHarness( - interceptors: [ - .requestCounter(counter1), - .requestCounter(counter2), - ] - ) - - try await harness.execute(handler: .echo) { inbound in - try await inbound.write(.metadata([:])) - await inbound.finish() - } consumer: { outbound in - let parts = try await outbound.collect() - XCTAssertEqual(parts, [.metadata([:]), .status(.ok, [:])]) - } - - XCTAssertEqual(counter1.value, 1) - XCTAssertEqual(counter2.value, 1) - } - - func testInterceptorsAreCalledInOrder() async throws { - let counter1 = AtomicCounter() - let counter2 = AtomicCounter() - - // The interceptor skips the handler altogether. - let harness = ServerRPCExecutorTestHarness( - interceptors: [ - .requestCounter(counter1), - .rejectAll(with: RPCError(code: .unavailable, message: "")), - .requestCounter(counter2), - ] - ) - - try await harness.execute(handler: .echo) { inbound in - try await inbound.write(.metadata([:])) - await inbound.finish() - } consumer: { outbound in - let parts = try await outbound.collect() - XCTAssertEqual(parts, [.status(Status(code: .unavailable, message: ""), [:])]) - } - - XCTAssertEqual(counter1.value, 1) - // Zero because the RPC should've been rejected by the second interceptor. - XCTAssertEqual(counter2.value, 0) - } - - func testThrowingInterceptor() async throws { - let harness = ServerRPCExecutorTestHarness( - interceptors: [.throwError(RPCError(code: .unavailable, message: "Unavailable"))] - ) - - try await harness.execute(handler: .echo) { inbound in - try await inbound.write(.metadata([:])) - await inbound.finish() - } consumer: { outbound in - let parts = try await outbound.collect() - XCTAssertEqual(parts, [.status(Status(code: .unavailable, message: "Unavailable"), [:])]) - } - } -} diff --git a/Tests/GRPCCoreTests/Call/Server/RPCRouterTests.swift b/Tests/GRPCCoreTests/Call/Server/RPCRouterTests.swift deleted file mode 100644 index 86fe1bd1e..000000000 --- a/Tests/GRPCCoreTests/Call/Server/RPCRouterTests.swift +++ /dev/null @@ -1,62 +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. - */ - -import GRPCCore -import XCTest - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -final class RPCRouterTests: XCTestCase { - func testEmptyRouter() async throws { - var router = RPCRouter() - XCTAssertEqual(router.count, 0) - XCTAssertEqual(router.methods, []) - XCTAssertFalse(router.hasHandler(forMethod: MethodDescriptor(service: "foo", method: "bar"))) - XCTAssertFalse(router.removeHandler(forMethod: MethodDescriptor(service: "foo", method: "bar"))) - } - - func testRegisterMethod() async throws { - var router = RPCRouter() - let method = MethodDescriptor(service: "foo", method: "bar") - router.registerHandler( - forMethod: method, - deserializer: IdentityDeserializer(), - serializer: IdentitySerializer() - ) { _, _ in - throw RPCError(code: .failedPrecondition, message: "Shouldn't be called") - } - - XCTAssertEqual(router.count, 1) - XCTAssertEqual(router.methods, [method]) - XCTAssertTrue(router.hasHandler(forMethod: method)) - } - - func testRemoveMethod() async throws { - var router = RPCRouter() - let method = MethodDescriptor(service: "foo", method: "bar") - router.registerHandler( - forMethod: method, - deserializer: IdentityDeserializer(), - serializer: IdentitySerializer() - ) { _, _ in - throw RPCError(code: .failedPrecondition, message: "Shouldn't be called") - } - - XCTAssertTrue(router.removeHandler(forMethod: method)) - XCTAssertFalse(router.hasHandler(forMethod: method)) - XCTAssertEqual(router.count, 0) - XCTAssertEqual(router.methods, []) - } -} diff --git a/Tests/GRPCCoreTests/Call/Server/ServerRequestTests.swift b/Tests/GRPCCoreTests/Call/Server/ServerRequestTests.swift deleted file mode 100644 index 532e5e51c..000000000 --- a/Tests/GRPCCoreTests/Call/Server/ServerRequestTests.swift +++ /dev/null @@ -1,29 +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. - */ -@_spi(Testing) import GRPCCore -import XCTest - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -final class ServerRequestTests: XCTestCase { - func testSingleToStreamConversion() async throws { - let single = ServerRequest.Single(metadata: ["bar": "baz"], message: "foo") - let stream = ServerRequest.Stream(single: single) - - XCTAssertEqual(stream.metadata, ["bar": "baz"]) - let collected = try await stream.messages.collect() - XCTAssertEqual(collected, ["foo"]) - } -} diff --git a/Tests/GRPCCoreTests/Call/Server/ServerResponseTests.swift b/Tests/GRPCCoreTests/Call/Server/ServerResponseTests.swift deleted file mode 100644 index d5614e906..000000000 --- a/Tests/GRPCCoreTests/Call/Server/ServerResponseTests.swift +++ /dev/null @@ -1,106 +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. - */ -@_spi(Testing) import GRPCCore -import XCTest - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -final class ServerResponseTests: XCTestCase { - func testSingleConvenienceInit() { - var response = ServerResponse.Single( - message: "message", - metadata: ["metadata": "initial"], - trailingMetadata: ["metadata": "trailing"] - ) - - switch response.accepted { - case .success(let contents): - XCTAssertEqual(contents.message, "message") - XCTAssertEqual(contents.metadata, ["metadata": "initial"]) - XCTAssertEqual(contents.trailingMetadata, ["metadata": "trailing"]) - case .failure: - XCTFail("Unexpected error") - } - - let error = RPCError(code: .aborted, message: "Aborted") - response = ServerResponse.Single(of: String.self, error: error) - switch response.accepted { - case .success: - XCTFail("Unexpected success") - case .failure(let error): - XCTAssertEqual(error, error) - } - } - - func testStreamConvenienceInit() async throws { - var response = ServerResponse.Stream(of: String.self, metadata: ["metadata": "initial"]) { _ in - // Empty body. - return ["metadata": "trailing"] - } - - switch response.accepted { - case .success(let contents): - XCTAssertEqual(contents.metadata, ["metadata": "initial"]) - let trailingMetadata = try await contents.producer(.failTestOnWrite()) - XCTAssertEqual(trailingMetadata, ["metadata": "trailing"]) - case .failure: - XCTFail("Unexpected error") - } - - let error = RPCError(code: .aborted, message: "Aborted") - response = ServerResponse.Stream(of: String.self, error: error) - switch response.accepted { - case .success: - XCTFail("Unexpected success") - case .failure(let error): - XCTAssertEqual(error, error) - } - } - - func testSingleToStreamConversionForSuccessfulResponse() async throws { - let single = ServerResponse.Single( - message: "foo", - metadata: ["metadata": "initial"], - trailingMetadata: ["metadata": "trailing"] - ) - - let stream = ServerResponse.Stream(single: single) - let (messages, continuation) = AsyncStream.makeStream(of: String.self) - let trailingMetadata: Metadata - - switch stream.accepted { - case .success(let contents): - trailingMetadata = try await contents.producer(.gathering(into: continuation)) - continuation.finish() - case .failure(let error): - throw error - } - - XCTAssertEqual(stream.metadata, ["metadata": "initial"]) - let collected = try await messages.collect() - XCTAssertEqual(collected, ["foo"]) - XCTAssertEqual(trailingMetadata, ["metadata": "trailing"]) - } - - func testSingleToStreamConversionForFailedResponse() async throws { - let error = RPCError(code: .aborted, message: "aborted") - let single = ServerResponse.Single(of: String.self, error: error) - let stream = ServerResponse.Stream(single: single) - - XCTAssertThrowsRPCError(try stream.accepted.get()) { - XCTAssertEqual($0, error) - } - } -} diff --git a/Tests/GRPCCoreTests/Coding/CodingTests.swift b/Tests/GRPCCoreTests/Coding/CodingTests.swift deleted file mode 100644 index efb57f94f..000000000 --- a/Tests/GRPCCoreTests/Coding/CodingTests.swift +++ /dev/null @@ -1,42 +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. - */ -import GRPCCore -import XCTest - -final class CodingTests: XCTestCase { - func testJSONRoundtrip() throws { - // This test just demonstrates that the API is suitable. - - struct Message: Codable, Hashable { - var foo: String - var bar: Int - var baz: Baz - - struct Baz: Codable, Hashable { - var bazzy: Double - } - } - - let message = Message(foo: "foo", bar: 42, baz: .init(bazzy: 3.1415)) - - let serializer = JSONSerializer() - let deserializer = JSONDeserializer() - - let bytes = try serializer.serialize(message) - let roundTrip = try deserializer.deserialize(bytes) - XCTAssertEqual(roundTrip, message) - } -} diff --git a/Tests/GRPCCoreTests/Coding/CompressionAlgorithmTests.swift b/Tests/GRPCCoreTests/Coding/CompressionAlgorithmTests.swift deleted file mode 100644 index 351538816..000000000 --- a/Tests/GRPCCoreTests/Coding/CompressionAlgorithmTests.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. - */ - -import GRPCCore -import XCTest - -final class CompressionAlgorithmTests: XCTestCase { - func testCompressionAlgorithmSetContains() { - var algorithms = CompressionAlgorithmSet() - XCTAssertFalse(algorithms.contains(.gzip)) - XCTAssertFalse(algorithms.contains(.deflate)) - XCTAssertFalse(algorithms.contains(.none)) - - algorithms.formUnion(.gzip) - XCTAssertTrue(algorithms.contains(.gzip)) - XCTAssertFalse(algorithms.contains(.deflate)) - XCTAssertFalse(algorithms.contains(.none)) - - algorithms.formUnion(.deflate) - XCTAssertTrue(algorithms.contains(.gzip)) - XCTAssertTrue(algorithms.contains(.deflate)) - XCTAssertFalse(algorithms.contains(.none)) - - algorithms.formUnion(.none) - XCTAssertTrue(algorithms.contains(.gzip)) - XCTAssertTrue(algorithms.contains(.deflate)) - XCTAssertTrue(algorithms.contains(.none)) - } - - func testCompressionAlgorithmSetElements() { - var algorithms = CompressionAlgorithmSet.all - XCTAssertEqual(Array(algorithms.elements), [.none, .deflate, .gzip]) - - algorithms.subtract(.deflate) - XCTAssertEqual(Array(algorithms.elements), [.none, .gzip]) - - algorithms.subtract(.none) - XCTAssertEqual(Array(algorithms.elements), [.gzip]) - - algorithms.subtract(.gzip) - XCTAssertEqual(Array(algorithms.elements), []) - } - - func testCompressionAlgorithmSetElementsIgnoresUnknownBits() { - let algorithms = CompressionAlgorithmSet(rawValue: .max) - XCTAssertEqual(Array(algorithms.elements), [.none, .deflate, .gzip]) - } -} diff --git a/Tests/GRPCCoreTests/Configuration/Generated/code.pb.swift b/Tests/GRPCCoreTests/Configuration/Generated/code.pb.swift deleted file mode 100644 index 75e9e725a..000000000 --- a/Tests/GRPCCoreTests/Configuration/Generated/code.pb.swift +++ /dev/null @@ -1,297 +0,0 @@ -// DO NOT EDIT. -// swift-format-ignore-file -// swiftlint:disable all -// -// Generated by the Swift generator plugin for the protocol buffer compiler. -// Source: google/rpc/code.proto -// -// For information on using the generated types, please see the documentation: -// https://github.com/apple/swift-protobuf/ - -// Copyright 2024 Google LLC -// -// 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 canonical error codes for gRPC APIs. -/// -/// -/// Sometimes multiple error codes may apply. Services should return -/// the most specific error code that applies. For example, prefer -/// `OUT_OF_RANGE` over `FAILED_PRECONDITION` if both codes apply. -/// Similarly prefer `NOT_FOUND` or `ALREADY_EXISTS` over `FAILED_PRECONDITION`. -enum Google_Rpc_Code: SwiftProtobuf.Enum, Swift.CaseIterable { - typealias RawValue = Int - - /// Not an error; returned on success. - /// - /// HTTP Mapping: 200 OK - case ok // = 0 - - /// The operation was cancelled, typically by the caller. - /// - /// HTTP Mapping: 499 Client Closed Request - case cancelled // = 1 - - /// Unknown error. For example, this error may be returned when - /// 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. - /// - /// HTTP Mapping: 500 Internal Server Error - case unknown // = 2 - - /// The client specified an invalid argument. Note that this differs - /// from `FAILED_PRECONDITION`. `INVALID_ARGUMENT` indicates arguments - /// that are problematic regardless of the state of the system - /// (e.g., a malformed file name). - /// - /// HTTP Mapping: 400 Bad Request - case invalidArgument // = 3 - - /// The deadline expired before the 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. - /// - /// HTTP Mapping: 504 Gateway Timeout - case deadlineExceeded // = 4 - - /// Some requested entity (e.g., file or directory) was not found. - /// - /// Note to server developers: if a request is denied for an entire class - /// of users, such as gradual feature rollout or undocumented allowlist, - /// `NOT_FOUND` may be used. If a request is denied for some users within - /// a class of users, such as user-based access control, `PERMISSION_DENIED` - /// must be used. - /// - /// HTTP Mapping: 404 Not Found - case notFound // = 5 - - /// The entity that a client attempted to create (e.g., file or directory) - /// already exists. - /// - /// HTTP Mapping: 409 Conflict - case alreadyExists // = 6 - - /// The caller does not have permission to execute the specified - /// operation. `PERMISSION_DENIED` must not be used for rejections - /// caused by exhausting some resource (use `RESOURCE_EXHAUSTED` - /// instead for those errors). `PERMISSION_DENIED` must not be - /// used if the caller can not be identified (use `UNAUTHENTICATED` - /// instead for those errors). This error code does not imply the - /// request is valid or the requested entity exists or satisfies - /// other pre-conditions. - /// - /// HTTP Mapping: 403 Forbidden - case permissionDenied // = 7 - - /// The request does not have valid authentication credentials for the - /// operation. - /// - /// HTTP Mapping: 401 Unauthorized - case unauthenticated // = 16 - - /// Some resource has been exhausted, perhaps a per-user quota, or - /// perhaps the entire file system is out of space. - /// - /// HTTP Mapping: 429 Too Many Requests - case resourceExhausted // = 8 - - /// The operation was rejected because the system is not in a state - /// required for the operation's execution. For example, the directory - /// to be deleted is non-empty, an rmdir operation is applied to - /// a non-directory, etc. - /// - /// Service implementors can use the following guidelines to decide - /// between `FAILED_PRECONDITION`, `ABORTED`, and `UNAVAILABLE`: - /// (a) Use `UNAVAILABLE` if the client can retry just the failing call. - /// (b) Use `ABORTED` if the client should retry at a higher level. For - /// example, when a client-specified test-and-set fails, indicating the - /// client should restart a read-modify-write sequence. - /// (c) Use `FAILED_PRECONDITION` if the client should not retry until - /// the system state has been explicitly fixed. For example, if an "rmdir" - /// fails because the directory is non-empty, `FAILED_PRECONDITION` - /// should be returned since the client should not retry unless - /// the files are deleted from the directory. - /// - /// HTTP Mapping: 400 Bad Request - case failedPrecondition // = 9 - - /// The operation was aborted, typically due to a concurrency issue such as - /// a sequencer check failure or transaction abort. - /// - /// See the guidelines above for deciding between `FAILED_PRECONDITION`, - /// `ABORTED`, and `UNAVAILABLE`. - /// - /// HTTP Mapping: 409 Conflict - case aborted // = 10 - - /// The operation was attempted past the valid range. E.g., seeking or - /// reading past end-of-file. - /// - /// Unlike `INVALID_ARGUMENT`, this error indicates a problem that may - /// be fixed if the system state changes. For example, a 32-bit file - /// system will generate `INVALID_ARGUMENT` if asked to read at an - /// offset that is not in the range [0,2^32-1], but it will generate - /// `OUT_OF_RANGE` if asked to read from an offset past the current - /// file size. - /// - /// There is a fair bit of overlap between `FAILED_PRECONDITION` and - /// `OUT_OF_RANGE`. We recommend using `OUT_OF_RANGE` (the more specific - /// error) when it applies so that callers who are iterating through - /// a space can easily look for an `OUT_OF_RANGE` error to detect when - /// they are done. - /// - /// HTTP Mapping: 400 Bad Request - case outOfRange // = 11 - - /// The operation is not implemented or is not supported/enabled in this - /// service. - /// - /// HTTP Mapping: 501 Not Implemented - case unimplemented // = 12 - - /// Internal errors. This means that some invariants expected by the - /// underlying system have been broken. This error code is reserved - /// for serious errors. - /// - /// HTTP Mapping: 500 Internal Server Error - case `internal` // = 13 - - /// The service is currently unavailable. This is most likely a - /// transient condition, which can be corrected by retrying with - /// a backoff. Note that it is not always safe to retry - /// non-idempotent operations. - /// - /// See the guidelines above for deciding between `FAILED_PRECONDITION`, - /// `ABORTED`, and `UNAVAILABLE`. - /// - /// HTTP Mapping: 503 Service Unavailable - case unavailable // = 14 - - /// Unrecoverable data loss or corruption. - /// - /// HTTP Mapping: 500 Internal Server Error - case dataLoss // = 15 - case UNRECOGNIZED(Int) - - init() { - self = .ok - } - - init?(rawValue: Int) { - switch rawValue { - case 0: self = .ok - case 1: self = .cancelled - case 2: self = .unknown - case 3: self = .invalidArgument - case 4: self = .deadlineExceeded - case 5: self = .notFound - case 6: self = .alreadyExists - case 7: self = .permissionDenied - case 8: self = .resourceExhausted - case 9: self = .failedPrecondition - case 10: self = .aborted - case 11: self = .outOfRange - case 12: self = .unimplemented - case 13: self = .internal - case 14: self = .unavailable - case 15: self = .dataLoss - case 16: self = .unauthenticated - default: self = .UNRECOGNIZED(rawValue) - } - } - - var rawValue: Int { - switch self { - case .ok: return 0 - case .cancelled: return 1 - case .unknown: return 2 - case .invalidArgument: return 3 - case .deadlineExceeded: return 4 - case .notFound: return 5 - case .alreadyExists: return 6 - case .permissionDenied: return 7 - case .resourceExhausted: return 8 - case .failedPrecondition: return 9 - case .aborted: return 10 - case .outOfRange: return 11 - case .unimplemented: return 12 - case .internal: return 13 - case .unavailable: return 14 - case .dataLoss: return 15 - case .unauthenticated: return 16 - case .UNRECOGNIZED(let i): return i - } - } - - // The compiler won't synthesize support with the UNRECOGNIZED case. - static let allCases: [Google_Rpc_Code] = [ - .ok, - .cancelled, - .unknown, - .invalidArgument, - .deadlineExceeded, - .notFound, - .alreadyExists, - .permissionDenied, - .unauthenticated, - .resourceExhausted, - .failedPrecondition, - .aborted, - .outOfRange, - .unimplemented, - .internal, - .unavailable, - .dataLoss, - ] - -} - -// MARK: - Code below here is support for the SwiftProtobuf runtime. - -extension Google_Rpc_Code: SwiftProtobuf._ProtoNameProviding { - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 0: .same(proto: "OK"), - 1: .same(proto: "CANCELLED"), - 2: .same(proto: "UNKNOWN"), - 3: .same(proto: "INVALID_ARGUMENT"), - 4: .same(proto: "DEADLINE_EXCEEDED"), - 5: .same(proto: "NOT_FOUND"), - 6: .same(proto: "ALREADY_EXISTS"), - 7: .same(proto: "PERMISSION_DENIED"), - 8: .same(proto: "RESOURCE_EXHAUSTED"), - 9: .same(proto: "FAILED_PRECONDITION"), - 10: .same(proto: "ABORTED"), - 11: .same(proto: "OUT_OF_RANGE"), - 12: .same(proto: "UNIMPLEMENTED"), - 13: .same(proto: "INTERNAL"), - 14: .same(proto: "UNAVAILABLE"), - 15: .same(proto: "DATA_LOSS"), - 16: .same(proto: "UNAUTHENTICATED"), - ] -} diff --git a/Tests/GRPCCoreTests/Configuration/Generated/rls.pb.swift b/Tests/GRPCCoreTests/Configuration/Generated/rls.pb.swift deleted file mode 100644 index 36f8887af..000000000 --- a/Tests/GRPCCoreTests/Configuration/Generated/rls.pb.swift +++ /dev/null @@ -1,241 +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/lookup/v1/rls.proto -// -// For information on using the generated types, please see the documentation: -// https://github.com/apple/swift-protobuf/ - -// Copyright 2020 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. - -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_Lookup_V1_RouteLookupRequest: 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. - - /// Target type allows the client to specify what kind of target format it - /// would like from RLS to allow it to find the regional server, e.g. "grpc". - var targetType: String = String() - - /// Reason for making this request. - var reason: Grpc_Lookup_V1_RouteLookupRequest.Reason = .unknown - - /// For REASON_STALE, the header_data from the stale response, if any. - var staleHeaderData: String = String() - - /// Map of key values extracted via key builders for the gRPC or HTTP request. - var keyMap: Dictionary = [:] - - /// Application-specific optional extensions. - var extensions: [SwiftProtobuf.Google_Protobuf_Any] = [] - - var unknownFields = SwiftProtobuf.UnknownStorage() - - /// Possible reasons for making a request. - enum Reason: SwiftProtobuf.Enum, Swift.CaseIterable { - typealias RawValue = Int - - /// Unused - case unknown // = 0 - - /// No data available in local cache - case miss // = 1 - - /// Data in local cache is stale - case stale // = 2 - case UNRECOGNIZED(Int) - - init() { - self = .unknown - } - - init?(rawValue: Int) { - switch rawValue { - case 0: self = .unknown - case 1: self = .miss - case 2: self = .stale - default: self = .UNRECOGNIZED(rawValue) - } - } - - var rawValue: Int { - switch self { - case .unknown: return 0 - case .miss: return 1 - case .stale: return 2 - case .UNRECOGNIZED(let i): return i - } - } - - // The compiler won't synthesize support with the UNRECOGNIZED case. - static let allCases: [Grpc_Lookup_V1_RouteLookupRequest.Reason] = [ - .unknown, - .miss, - .stale, - ] - - } - - init() {} -} - -struct Grpc_Lookup_V1_RouteLookupResponse: 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. - - /// Prioritized list (best one first) of addressable entities to use - /// for routing, using syntax requested by the request target_type. - /// The targets will be tried in order until a healthy one is found. - var targets: [String] = [] - - /// Optional header value to pass along to AFE in the X-Google-RLS-Data header. - /// Cached with "target" and sent with all requests that match the request key. - /// Allows the RLS to pass its work product to the eventual target. - var headerData: String = String() - - /// Application-specific optional extensions. - var extensions: [SwiftProtobuf.Google_Protobuf_Any] = [] - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -// MARK: - Code below here is support for the SwiftProtobuf runtime. - -fileprivate let _protobuf_package = "grpc.lookup.v1" - -extension Grpc_Lookup_V1_RouteLookupRequest: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".RouteLookupRequest" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 3: .standard(proto: "target_type"), - 5: .same(proto: "reason"), - 6: .standard(proto: "stale_header_data"), - 4: .standard(proto: "key_map"), - 7: .same(proto: "extensions"), - ] - - 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 3: try { try decoder.decodeSingularStringField(value: &self.targetType) }() - case 4: try { try decoder.decodeMapField(fieldType: SwiftProtobuf._ProtobufMap.self, value: &self.keyMap) }() - case 5: try { try decoder.decodeSingularEnumField(value: &self.reason) }() - case 6: try { try decoder.decodeSingularStringField(value: &self.staleHeaderData) }() - case 7: try { try decoder.decodeRepeatedMessageField(value: &self.extensions) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if !self.targetType.isEmpty { - try visitor.visitSingularStringField(value: self.targetType, fieldNumber: 3) - } - if !self.keyMap.isEmpty { - try visitor.visitMapField(fieldType: SwiftProtobuf._ProtobufMap.self, value: self.keyMap, fieldNumber: 4) - } - if self.reason != .unknown { - try visitor.visitSingularEnumField(value: self.reason, fieldNumber: 5) - } - if !self.staleHeaderData.isEmpty { - try visitor.visitSingularStringField(value: self.staleHeaderData, fieldNumber: 6) - } - if !self.extensions.isEmpty { - try visitor.visitRepeatedMessageField(value: self.extensions, fieldNumber: 7) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Lookup_V1_RouteLookupRequest, rhs: Grpc_Lookup_V1_RouteLookupRequest) -> Bool { - if lhs.targetType != rhs.targetType {return false} - if lhs.reason != rhs.reason {return false} - if lhs.staleHeaderData != rhs.staleHeaderData {return false} - if lhs.keyMap != rhs.keyMap {return false} - if lhs.extensions != rhs.extensions {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Lookup_V1_RouteLookupRequest.Reason: SwiftProtobuf._ProtoNameProviding { - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 0: .same(proto: "REASON_UNKNOWN"), - 1: .same(proto: "REASON_MISS"), - 2: .same(proto: "REASON_STALE"), - ] -} - -extension Grpc_Lookup_V1_RouteLookupResponse: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".RouteLookupResponse" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 3: .same(proto: "targets"), - 2: .standard(proto: "header_data"), - 4: .same(proto: "extensions"), - ] - - 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 2: try { try decoder.decodeSingularStringField(value: &self.headerData) }() - case 3: try { try decoder.decodeRepeatedStringField(value: &self.targets) }() - case 4: try { try decoder.decodeRepeatedMessageField(value: &self.extensions) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if !self.headerData.isEmpty { - try visitor.visitSingularStringField(value: self.headerData, fieldNumber: 2) - } - if !self.targets.isEmpty { - try visitor.visitRepeatedStringField(value: self.targets, fieldNumber: 3) - } - if !self.extensions.isEmpty { - try visitor.visitRepeatedMessageField(value: self.extensions, fieldNumber: 4) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Lookup_V1_RouteLookupResponse, rhs: Grpc_Lookup_V1_RouteLookupResponse) -> Bool { - if lhs.targets != rhs.targets {return false} - if lhs.headerData != rhs.headerData {return false} - if lhs.extensions != rhs.extensions {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} diff --git a/Tests/GRPCCoreTests/Configuration/Generated/rls_config.pb.swift b/Tests/GRPCCoreTests/Configuration/Generated/rls_config.pb.swift deleted file mode 100644 index 879269999..000000000 --- a/Tests/GRPCCoreTests/Configuration/Generated/rls_config.pb.swift +++ /dev/null @@ -1,697 +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/lookup/v1/rls_config.proto -// -// For information on using the generated types, please see the documentation: -// https://github.com/apple/swift-protobuf/ - -// Copyright 2020 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. - -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 -} - -/// Extract a key based on a given name (e.g. header name or query parameter -/// name). The name must match one of the names listed in the "name" field. If -/// the "required_match" field is true, one of the specified names must be -/// present for the keybuilder to match. -struct Grpc_Lookup_V1_NameMatcher: 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 that will be used in the RLS key_map to refer to this value. - /// If required_match is true, you may omit this field or set it to an empty - /// string, in which case the matcher will require a match, but won't update - /// the key_map. - var key: String = String() - - /// Ordered list of names (headers or query parameter names) that can supply - /// this value; the first one with a non-empty value is used. - var names: [String] = [] - - /// If true, make this extraction required; the key builder will not match - /// if no value is found. - var requiredMatch: Bool = false - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -/// A GrpcKeyBuilder applies to a given gRPC service, name, and headers. -struct Grpc_Lookup_V1_GrpcKeyBuilder: 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 names: [Grpc_Lookup_V1_GrpcKeyBuilder.Name] = [] - - var extraKeys: Grpc_Lookup_V1_GrpcKeyBuilder.ExtraKeys { - get {return _extraKeys ?? Grpc_Lookup_V1_GrpcKeyBuilder.ExtraKeys()} - set {_extraKeys = newValue} - } - /// Returns true if `extraKeys` has been explicitly set. - var hasExtraKeys: Bool {return self._extraKeys != nil} - /// Clears the value of `extraKeys`. Subsequent reads from it will return its default value. - mutating func clearExtraKeys() {self._extraKeys = nil} - - /// Extract keys from all listed headers. - /// For gRPC, it is an error to specify "required_match" on the NameMatcher - /// protos. - var headers: [Grpc_Lookup_V1_NameMatcher] = [] - - /// You can optionally set one or more specific key/value pairs to be added to - /// the key_map. This can be useful to identify which builder built the key, - /// for example if you are suppressing the actual method, but need to - /// separately cache and request all the matched methods. - var constantKeys: Dictionary = [:] - - var unknownFields = SwiftProtobuf.UnknownStorage() - - /// To match, one of the given Name fields must match; the service and method - /// fields are specified as fixed strings. The service name is required and - /// includes the proto package name. The method name may be omitted, in - /// which case any method on the given service is matched. - struct Name: 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 service: String = String() - - var method: String = String() - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - } - - /// If you wish to include the host, service, or method names as keys in the - /// generated RouteLookupRequest, specify key names to use in the extra_keys - /// submessage. If a key name is empty, no key will be set for that value. - /// If this submessage is specified, the normal host/path fields will be left - /// unset in the RouteLookupRequest. We are deprecating host/path in the - /// RouteLookupRequest, so services should migrate to the ExtraKeys approach. - struct ExtraKeys: 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 host: String = String() - - var service: String = String() - - var method: String = String() - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - } - - init() {} - - fileprivate var _extraKeys: Grpc_Lookup_V1_GrpcKeyBuilder.ExtraKeys? = nil -} - -/// An HttpKeyBuilder applies to a given HTTP URL and headers. -/// -/// Path and host patterns use the matching syntax from gRPC transcoding to -/// extract named key/value pairs from the path and host components of the URL: -/// https://github.com/googleapis/googleapis/blob/master/google/api/http.proto -/// -/// It is invalid to specify the same key name in multiple places in a pattern. -/// -/// For a service where the project id can be expressed either as a subdomain or -/// in the path, separate HttpKeyBuilders must be used: -/// host_pattern: 'example.com' path_pattern: '/{id}/{object}/**' -/// host_pattern: '{id}.example.com' path_pattern: '/{object}/**' -/// If the host is exactly 'example.com', the first path segment will be used as -/// the id and the second segment as the object. If the host has a subdomain, the -/// subdomain will be used as the id and the first segment as the object. If -/// neither pattern matches, no keys will be extracted. -struct Grpc_Lookup_V1_HttpKeyBuilder: 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. - - /// host_pattern is an ordered list of host template patterns for the desired - /// value. If any host_pattern values are specified, then at least one must - /// match, and the last one wins and sets any specified variables. A host - /// consists of labels separated by dots. Each label is matched against the - /// label in the pattern as follows: - /// - "*": Matches any single label. - /// - "**": Matches zero or more labels (first or last part of host only). - /// - "{=...}": One or more label capture, where "..." can be any - /// template that does not include a capture. - /// - "{}": A single label capture. Identical to {=*}. - /// - /// Examples: - /// - "example.com": Only applies to the exact host example.com. - /// - "*.example.com": Matches subdomains of example.com. - /// - "**.example.com": matches example.com, and all levels of subdomains. - /// - "{project}.example.com": Extracts the third level subdomain. - /// - "{project=**}.example.com": Extracts the third level+ subdomains. - /// - "{project=**}": Extracts the entire host. - var hostPatterns: [String] = [] - - /// path_pattern is an ordered list of path template patterns for the desired - /// value. If any path_pattern values are specified, then at least one must - /// match, and the last one wins and sets any specified variables. A path - /// consists of segments separated by slashes. Each segment is matched against - /// the segment in the pattern as follows: - /// - "*": Matches any single segment. - /// - "**": Matches zero or more segments (first or last part of path only). - /// - "{=...}": One or more segment capture, where "..." can be any - /// template that does not include a capture. - /// - "{}": A single segment capture. Identical to {=*}. - /// A custom method may also be specified by appending ":" and the custom - /// method name or "*" to indicate any custom method (including no custom - /// method). For example, "/*/projects/{project_id}/**:*" extracts - /// `{project_id}` for any version, resource and custom method that includes - /// it. By default, any custom method will be matched. - /// - /// Examples: - /// - "/v1/{name=messages/*}": extracts a name like "messages/12345". - /// - "/v1/messages/{message_id}": extracts a message_id like "12345". - /// - "/v1/users/{user_id}/messages/{message_id}": extracts two key values. - var pathPatterns: [String] = [] - - /// List of query parameter names to try to match. - /// For example: ["parent", "name", "resource.name"] - /// We extract all the specified query_parameters (case-sensitively). If any - /// are marked as "required_match" and are not present, this keybuilder fails - /// to match. If a given parameter appears multiple times (?foo=a&foo=b) we - /// will report it as a comma-separated string (foo=a,b). - var queryParameters: [Grpc_Lookup_V1_NameMatcher] = [] - - /// List of headers to try to match. - /// We extract all the specified header values (case-insensitively). If any - /// are marked as "required_match" and are not present, this keybuilder fails - /// to match. If a given header appears multiple times in the request we will - /// report it as a comma-separated string, in standard HTTP fashion. - var headers: [Grpc_Lookup_V1_NameMatcher] = [] - - /// You can optionally set one or more specific key/value pairs to be added to - /// the key_map. This can be useful to identify which builder built the key, - /// for example if you are suppressing a lot of information from the URL, but - /// need to separately cache and request URLs with that content. - var constantKeys: Dictionary = [:] - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -struct Grpc_Lookup_V1_RouteLookupConfig: 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. - - /// Ordered specifications for constructing keys for HTTP requests. Last - /// match wins. If no HttpKeyBuilder matches, an empty key_map will be sent to - /// the lookup service; it should likely reply with a global default route - /// and raise an alert. - var httpKeybuilders: [Grpc_Lookup_V1_HttpKeyBuilder] = [] - - /// Unordered specifications for constructing keys for gRPC requests. All - /// GrpcKeyBuilders on this list must have unique "name" fields so that the - /// client is free to prebuild a hash map keyed by name. If no GrpcKeyBuilder - /// matches, an empty key_map will be sent to the lookup service; it should - /// likely reply with a global default route and raise an alert. - var grpcKeybuilders: [Grpc_Lookup_V1_GrpcKeyBuilder] = [] - - /// The name of the lookup service as a gRPC URI. Typically, this will be - /// a subdomain of the target, such as "lookup.datastore.googleapis.com". - var lookupService: String = String() - - /// Configure a timeout value for lookup service requests. - /// Defaults to 10 seconds if not specified. - var lookupServiceTimeout: SwiftProtobuf.Google_Protobuf_Duration { - get {return _lookupServiceTimeout ?? SwiftProtobuf.Google_Protobuf_Duration()} - set {_lookupServiceTimeout = newValue} - } - /// Returns true if `lookupServiceTimeout` has been explicitly set. - var hasLookupServiceTimeout: Bool {return self._lookupServiceTimeout != nil} - /// Clears the value of `lookupServiceTimeout`. Subsequent reads from it will return its default value. - mutating func clearLookupServiceTimeout() {self._lookupServiceTimeout = nil} - - /// How long are responses valid for (like HTTP Cache-Control). - /// If omitted or zero, the longest valid cache time is used. - /// This value is clamped to 5 minutes to avoid unflushable bad responses. - var maxAge: SwiftProtobuf.Google_Protobuf_Duration { - get {return _maxAge ?? SwiftProtobuf.Google_Protobuf_Duration()} - set {_maxAge = newValue} - } - /// Returns true if `maxAge` has been explicitly set. - var hasMaxAge: Bool {return self._maxAge != nil} - /// Clears the value of `maxAge`. Subsequent reads from it will return its default value. - mutating func clearMaxAge() {self._maxAge = nil} - - /// After a response has been in the client cache for this amount of time - /// and is re-requested, start an asynchronous RPC to re-validate it. - /// This value should be less than max_age by at least the length of a - /// typical RTT to the Route Lookup Service to fully mask the RTT latency. - /// If omitted, keys are only re-requested after they have expired. - var staleAge: SwiftProtobuf.Google_Protobuf_Duration { - get {return _staleAge ?? SwiftProtobuf.Google_Protobuf_Duration()} - set {_staleAge = newValue} - } - /// Returns true if `staleAge` has been explicitly set. - var hasStaleAge: Bool {return self._staleAge != nil} - /// Clears the value of `staleAge`. Subsequent reads from it will return its default value. - mutating func clearStaleAge() {self._staleAge = nil} - - /// Rough indicator of amount of memory to use for the client cache. Some of - /// the data structure overhead is not accounted for, so actual memory consumed - /// will be somewhat greater than this value. If this field is omitted or set - /// to zero, a client default will be used. The value may be capped to a lower - /// amount based on client configuration. - var cacheSizeBytes: Int64 = 0 - - /// This is a list of all the possible targets that can be returned by the - /// lookup service. If a target not on this list is returned, it will be - /// treated the same as an unhealthy target. - var validTargets: [String] = [] - - /// This value provides a default target to use if needed. If set, it will be - /// used if RLS returns an error, times out, or returns an invalid response. - /// Note that requests can be routed only to a subdomain of the original - /// target, e.g. "us_east_1.cloudbigtable.googleapis.com". - var defaultTarget: String = String() - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _lookupServiceTimeout: SwiftProtobuf.Google_Protobuf_Duration? = nil - fileprivate var _maxAge: SwiftProtobuf.Google_Protobuf_Duration? = nil - fileprivate var _staleAge: SwiftProtobuf.Google_Protobuf_Duration? = nil -} - -/// RouteLookupClusterSpecifier is used in xDS to represent a cluster specifier -/// plugin for RLS. -struct Grpc_Lookup_V1_RouteLookupClusterSpecifier: 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 RLS config for this cluster specifier plugin instance. - var routeLookupConfig: Grpc_Lookup_V1_RouteLookupConfig { - get {return _routeLookupConfig ?? Grpc_Lookup_V1_RouteLookupConfig()} - set {_routeLookupConfig = newValue} - } - /// Returns true if `routeLookupConfig` has been explicitly set. - var hasRouteLookupConfig: Bool {return self._routeLookupConfig != nil} - /// Clears the value of `routeLookupConfig`. Subsequent reads from it will return its default value. - mutating func clearRouteLookupConfig() {self._routeLookupConfig = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _routeLookupConfig: Grpc_Lookup_V1_RouteLookupConfig? = nil -} - -// MARK: - Code below here is support for the SwiftProtobuf runtime. - -fileprivate let _protobuf_package = "grpc.lookup.v1" - -extension Grpc_Lookup_V1_NameMatcher: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".NameMatcher" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "key"), - 2: .same(proto: "names"), - 3: .standard(proto: "required_match"), - ] - - 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.decodeRepeatedStringField(value: &self.names) }() - case 3: try { try decoder.decodeSingularBoolField(value: &self.requiredMatch) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if !self.key.isEmpty { - try visitor.visitSingularStringField(value: self.key, fieldNumber: 1) - } - if !self.names.isEmpty { - try visitor.visitRepeatedStringField(value: self.names, fieldNumber: 2) - } - if self.requiredMatch != false { - try visitor.visitSingularBoolField(value: self.requiredMatch, fieldNumber: 3) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Lookup_V1_NameMatcher, rhs: Grpc_Lookup_V1_NameMatcher) -> Bool { - if lhs.key != rhs.key {return false} - if lhs.names != rhs.names {return false} - if lhs.requiredMatch != rhs.requiredMatch {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Lookup_V1_GrpcKeyBuilder: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".GrpcKeyBuilder" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "names"), - 3: .standard(proto: "extra_keys"), - 2: .same(proto: "headers"), - 4: .standard(proto: "constant_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.decodeRepeatedMessageField(value: &self.names) }() - case 2: try { try decoder.decodeRepeatedMessageField(value: &self.headers) }() - case 3: try { try decoder.decodeSingularMessageField(value: &self._extraKeys) }() - case 4: try { try decoder.decodeMapField(fieldType: SwiftProtobuf._ProtobufMap.self, value: &self.constantKeys) }() - 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.names.isEmpty { - try visitor.visitRepeatedMessageField(value: self.names, fieldNumber: 1) - } - if !self.headers.isEmpty { - try visitor.visitRepeatedMessageField(value: self.headers, fieldNumber: 2) - } - try { if let v = self._extraKeys { - try visitor.visitSingularMessageField(value: v, fieldNumber: 3) - } }() - if !self.constantKeys.isEmpty { - try visitor.visitMapField(fieldType: SwiftProtobuf._ProtobufMap.self, value: self.constantKeys, fieldNumber: 4) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Lookup_V1_GrpcKeyBuilder, rhs: Grpc_Lookup_V1_GrpcKeyBuilder) -> Bool { - if lhs.names != rhs.names {return false} - if lhs._extraKeys != rhs._extraKeys {return false} - if lhs.headers != rhs.headers {return false} - if lhs.constantKeys != rhs.constantKeys {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Lookup_V1_GrpcKeyBuilder.Name: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = Grpc_Lookup_V1_GrpcKeyBuilder.protoMessageName + ".Name" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "service"), - 2: .same(proto: "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.decodeSingularStringField(value: &self.service) }() - case 2: try { try decoder.decodeSingularStringField(value: &self.method) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if !self.service.isEmpty { - try visitor.visitSingularStringField(value: self.service, fieldNumber: 1) - } - if !self.method.isEmpty { - try visitor.visitSingularStringField(value: self.method, fieldNumber: 2) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Lookup_V1_GrpcKeyBuilder.Name, rhs: Grpc_Lookup_V1_GrpcKeyBuilder.Name) -> Bool { - if lhs.service != rhs.service {return false} - if lhs.method != rhs.method {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Lookup_V1_GrpcKeyBuilder.ExtraKeys: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = Grpc_Lookup_V1_GrpcKeyBuilder.protoMessageName + ".ExtraKeys" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "host"), - 2: .same(proto: "service"), - 3: .same(proto: "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.decodeSingularStringField(value: &self.host) }() - case 2: try { try decoder.decodeSingularStringField(value: &self.service) }() - case 3: try { try decoder.decodeSingularStringField(value: &self.method) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if !self.host.isEmpty { - try visitor.visitSingularStringField(value: self.host, fieldNumber: 1) - } - if !self.service.isEmpty { - try visitor.visitSingularStringField(value: self.service, fieldNumber: 2) - } - if !self.method.isEmpty { - try visitor.visitSingularStringField(value: self.method, fieldNumber: 3) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Lookup_V1_GrpcKeyBuilder.ExtraKeys, rhs: Grpc_Lookup_V1_GrpcKeyBuilder.ExtraKeys) -> Bool { - if lhs.host != rhs.host {return false} - if lhs.service != rhs.service {return false} - if lhs.method != rhs.method {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Lookup_V1_HttpKeyBuilder: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".HttpKeyBuilder" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "host_patterns"), - 2: .standard(proto: "path_patterns"), - 3: .standard(proto: "query_parameters"), - 4: .same(proto: "headers"), - 5: .standard(proto: "constant_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.decodeRepeatedStringField(value: &self.hostPatterns) }() - case 2: try { try decoder.decodeRepeatedStringField(value: &self.pathPatterns) }() - case 3: try { try decoder.decodeRepeatedMessageField(value: &self.queryParameters) }() - case 4: try { try decoder.decodeRepeatedMessageField(value: &self.headers) }() - case 5: try { try decoder.decodeMapField(fieldType: SwiftProtobuf._ProtobufMap.self, value: &self.constantKeys) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if !self.hostPatterns.isEmpty { - try visitor.visitRepeatedStringField(value: self.hostPatterns, fieldNumber: 1) - } - if !self.pathPatterns.isEmpty { - try visitor.visitRepeatedStringField(value: self.pathPatterns, fieldNumber: 2) - } - if !self.queryParameters.isEmpty { - try visitor.visitRepeatedMessageField(value: self.queryParameters, fieldNumber: 3) - } - if !self.headers.isEmpty { - try visitor.visitRepeatedMessageField(value: self.headers, fieldNumber: 4) - } - if !self.constantKeys.isEmpty { - try visitor.visitMapField(fieldType: SwiftProtobuf._ProtobufMap.self, value: self.constantKeys, fieldNumber: 5) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Lookup_V1_HttpKeyBuilder, rhs: Grpc_Lookup_V1_HttpKeyBuilder) -> Bool { - if lhs.hostPatterns != rhs.hostPatterns {return false} - if lhs.pathPatterns != rhs.pathPatterns {return false} - if lhs.queryParameters != rhs.queryParameters {return false} - if lhs.headers != rhs.headers {return false} - if lhs.constantKeys != rhs.constantKeys {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Lookup_V1_RouteLookupConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".RouteLookupConfig" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "http_keybuilders"), - 2: .standard(proto: "grpc_keybuilders"), - 3: .standard(proto: "lookup_service"), - 4: .standard(proto: "lookup_service_timeout"), - 5: .standard(proto: "max_age"), - 6: .standard(proto: "stale_age"), - 7: .standard(proto: "cache_size_bytes"), - 8: .standard(proto: "valid_targets"), - 9: .standard(proto: "default_target"), - ] - - 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.httpKeybuilders) }() - case 2: try { try decoder.decodeRepeatedMessageField(value: &self.grpcKeybuilders) }() - case 3: try { try decoder.decodeSingularStringField(value: &self.lookupService) }() - case 4: try { try decoder.decodeSingularMessageField(value: &self._lookupServiceTimeout) }() - case 5: try { try decoder.decodeSingularMessageField(value: &self._maxAge) }() - case 6: try { try decoder.decodeSingularMessageField(value: &self._staleAge) }() - case 7: try { try decoder.decodeSingularInt64Field(value: &self.cacheSizeBytes) }() - case 8: try { try decoder.decodeRepeatedStringField(value: &self.validTargets) }() - case 9: try { try decoder.decodeSingularStringField(value: &self.defaultTarget) }() - 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.httpKeybuilders.isEmpty { - try visitor.visitRepeatedMessageField(value: self.httpKeybuilders, fieldNumber: 1) - } - if !self.grpcKeybuilders.isEmpty { - try visitor.visitRepeatedMessageField(value: self.grpcKeybuilders, fieldNumber: 2) - } - if !self.lookupService.isEmpty { - try visitor.visitSingularStringField(value: self.lookupService, fieldNumber: 3) - } - try { if let v = self._lookupServiceTimeout { - try visitor.visitSingularMessageField(value: v, fieldNumber: 4) - } }() - try { if let v = self._maxAge { - try visitor.visitSingularMessageField(value: v, fieldNumber: 5) - } }() - try { if let v = self._staleAge { - try visitor.visitSingularMessageField(value: v, fieldNumber: 6) - } }() - if self.cacheSizeBytes != 0 { - try visitor.visitSingularInt64Field(value: self.cacheSizeBytes, fieldNumber: 7) - } - if !self.validTargets.isEmpty { - try visitor.visitRepeatedStringField(value: self.validTargets, fieldNumber: 8) - } - if !self.defaultTarget.isEmpty { - try visitor.visitSingularStringField(value: self.defaultTarget, fieldNumber: 9) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Lookup_V1_RouteLookupConfig, rhs: Grpc_Lookup_V1_RouteLookupConfig) -> Bool { - if lhs.httpKeybuilders != rhs.httpKeybuilders {return false} - if lhs.grpcKeybuilders != rhs.grpcKeybuilders {return false} - if lhs.lookupService != rhs.lookupService {return false} - if lhs._lookupServiceTimeout != rhs._lookupServiceTimeout {return false} - if lhs._maxAge != rhs._maxAge {return false} - if lhs._staleAge != rhs._staleAge {return false} - if lhs.cacheSizeBytes != rhs.cacheSizeBytes {return false} - if lhs.validTargets != rhs.validTargets {return false} - if lhs.defaultTarget != rhs.defaultTarget {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_Lookup_V1_RouteLookupClusterSpecifier: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".RouteLookupClusterSpecifier" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "route_lookup_config"), - ] - - 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._routeLookupConfig) }() - 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._routeLookupConfig { - try visitor.visitSingularMessageField(value: v, fieldNumber: 1) - } }() - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_Lookup_V1_RouteLookupClusterSpecifier, rhs: Grpc_Lookup_V1_RouteLookupClusterSpecifier) -> Bool { - if lhs._routeLookupConfig != rhs._routeLookupConfig {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} diff --git a/Tests/GRPCCoreTests/Configuration/Generated/service_config.pb.swift b/Tests/GRPCCoreTests/Configuration/Generated/service_config.pb.swift deleted file mode 100644 index c25062a78..000000000 --- a/Tests/GRPCCoreTests/Configuration/Generated/service_config.pb.swift +++ /dev/null @@ -1,4046 +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/service_config/service_config.proto -// -// For information on using the generated types, please see the documentation: -// https://github.com/apple/swift-protobuf/ - -// Copyright 2016 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. - -// A ServiceConfig is supplied when a service is deployed. It mostly contains -// parameters for how clients that connect to the service should behave (for -// example, the load balancing policy to use to pick between service replicas). -// -// The configuration options provided here act as overrides to automatically -// chosen option values. Service owners should be conservative in specifying -// options as the system is likely to choose better values for these options in -// the vast majority of cases. In other words, please specify a configuration -// option only if you really have to, and avoid copy-paste inclusion of configs. -// -// Note that gRPC uses the service config in JSON form, not in protobuf -// form. This proto definition is intended to help document the schema but -// will not actually be used directly by gRPC. - -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 -} - -/// Configuration for a method. -struct Grpc_ServiceConfig_MethodConfig: 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: [Grpc_ServiceConfig_MethodConfig.Name] = [] - - /// Whether RPCs sent to this method should wait until the connection is - /// ready by default. 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. - /// - /// The value specified via the gRPC client API will override the value - /// set here. However, note that setting the value in the client API will - /// also affect transient errors encountered during name resolution, which - /// cannot be caught by the value here, since the service config is - /// obtained by the gRPC client via name resolution. - var waitForReady: SwiftProtobuf.Google_Protobuf_BoolValue { - get {return _waitForReady ?? SwiftProtobuf.Google_Protobuf_BoolValue()} - set {_waitForReady = newValue} - } - /// Returns true if `waitForReady` has been explicitly set. - var hasWaitForReady: Bool {return self._waitForReady != nil} - /// Clears the value of `waitForReady`. Subsequent reads from it will return its default value. - mutating func clearWaitForReady() {self._waitForReady = nil} - - /// The default timeout in seconds for RPCs sent to this method. This can be - /// overridden in code. If no reply is received in the specified amount of - /// time, the request is aborted and a DEADLINE_EXCEEDED error status - /// is returned to the caller. - /// - /// The actual deadline used will be the minimum of the value specified here - /// and the value set by the application via the gRPC client API. If either - /// one is not set, then the other will be used. If neither is set, then the - /// request has no deadline. - var timeout: SwiftProtobuf.Google_Protobuf_Duration { - get {return _timeout ?? SwiftProtobuf.Google_Protobuf_Duration()} - set {_timeout = newValue} - } - /// Returns true if `timeout` has been explicitly set. - var hasTimeout: Bool {return self._timeout != nil} - /// Clears the value of `timeout`. Subsequent reads from it will return its default value. - mutating func clearTimeout() {self._timeout = nil} - - /// The maximum allowed payload size for an individual request or object in a - /// stream (client->server) in bytes. The size which is measured is the - /// serialized payload after per-message compression (but before stream - /// compression) in bytes. This applies both to streaming and non-streaming - /// requests. - /// - /// The actual value used is the minimum of the value specified here and the - /// value set by the application via the gRPC client API. If either one is - /// not set, then the other will be used. If neither is set, then the - /// built-in default is used. - /// - /// If a client attempts to send an object larger than this value, it will not - /// be sent and the client will see a ClientError. - /// Note that 0 is a valid value, meaning that the request message - /// must be empty. - var maxRequestMessageBytes: SwiftProtobuf.Google_Protobuf_UInt32Value { - get {return _maxRequestMessageBytes ?? SwiftProtobuf.Google_Protobuf_UInt32Value()} - set {_maxRequestMessageBytes = newValue} - } - /// Returns true if `maxRequestMessageBytes` has been explicitly set. - var hasMaxRequestMessageBytes: Bool {return self._maxRequestMessageBytes != nil} - /// Clears the value of `maxRequestMessageBytes`. Subsequent reads from it will return its default value. - mutating func clearMaxRequestMessageBytes() {self._maxRequestMessageBytes = nil} - - /// The maximum allowed payload size for an individual response or object in a - /// stream (server->client) in bytes. The size which is measured is the - /// serialized payload after per-message compression (but before stream - /// compression) in bytes. This applies both to streaming and non-streaming - /// requests. - /// - /// The actual value used is the minimum of the value specified here and the - /// value set by the application via the gRPC client API. If either one is - /// not set, then the other will be used. If neither is set, then the - /// built-in default is used. - /// - /// If a server attempts to send an object larger than this value, it will not - /// be sent, and a ServerError will be sent to the client instead. - /// Note that 0 is a valid value, meaning that the response message - /// must be empty. - var maxResponseMessageBytes: SwiftProtobuf.Google_Protobuf_UInt32Value { - get {return _maxResponseMessageBytes ?? SwiftProtobuf.Google_Protobuf_UInt32Value()} - set {_maxResponseMessageBytes = newValue} - } - /// Returns true if `maxResponseMessageBytes` has been explicitly set. - var hasMaxResponseMessageBytes: Bool {return self._maxResponseMessageBytes != nil} - /// Clears the value of `maxResponseMessageBytes`. Subsequent reads from it will return its default value. - mutating func clearMaxResponseMessageBytes() {self._maxResponseMessageBytes = nil} - - /// Only one of retry_policy or hedging_policy may be set. If neither is set, - /// RPCs will not be retried or hedged. - var retryOrHedgingPolicy: Grpc_ServiceConfig_MethodConfig.OneOf_RetryOrHedgingPolicy? = nil - - var retryPolicy: Grpc_ServiceConfig_MethodConfig.RetryPolicy { - get { - if case .retryPolicy(let v)? = retryOrHedgingPolicy {return v} - return Grpc_ServiceConfig_MethodConfig.RetryPolicy() - } - set {retryOrHedgingPolicy = .retryPolicy(newValue)} - } - - var hedgingPolicy: Grpc_ServiceConfig_MethodConfig.HedgingPolicy { - get { - if case .hedgingPolicy(let v)? = retryOrHedgingPolicy {return v} - return Grpc_ServiceConfig_MethodConfig.HedgingPolicy() - } - set {retryOrHedgingPolicy = .hedgingPolicy(newValue)} - } - - var unknownFields = SwiftProtobuf.UnknownStorage() - - /// Only one of retry_policy or hedging_policy may be set. If neither is set, - /// RPCs will not be retried or hedged. - enum OneOf_RetryOrHedgingPolicy: Equatable, Sendable { - case retryPolicy(Grpc_ServiceConfig_MethodConfig.RetryPolicy) - case hedgingPolicy(Grpc_ServiceConfig_MethodConfig.HedgingPolicy) - - } - - /// The names of the methods to which this configuration applies. - /// - MethodConfig without names (empty list) will be skipped. - /// - Each name entry must be unique across the entire ServiceConfig. - /// - If the 'method' field is empty, this MethodConfig specifies the defaults - /// for all methods for the specified service. - /// - If the 'service' field is empty, the 'method' field must be empty, and - /// this MethodConfig specifies the default for all methods (it's the default - /// config). - /// - /// When determining which MethodConfig to use for a given RPC, the most - /// specific match wins. For example, let's say that the service config - /// contains the following MethodConfig entries: - /// - /// method_config { name { } ... } - /// method_config { name { service: "MyService" } ... } - /// method_config { name { service: "MyService" method: "Foo" } ... } - /// - /// MyService/Foo will use the third entry, because it exactly matches the - /// service and method name. MyService/Bar will use the second entry, because - /// it provides the default for all methods of MyService. AnotherService/Baz - /// will use the first entry, because it doesn't match the other two. - /// - /// In JSON representation, value "", value `null`, and not present are the - /// same. The following are the same Name: - /// - { "service": "s" } - /// - { "service": "s", "method": null } - /// - { "service": "s", "method": "" } - struct Name: 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. - - /// Required. Includes proto package name. - var service: String = String() - - var method: String = String() - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - } - - /// The retry policy for outgoing RPCs. - struct RetryPolicy: 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 maximum number of RPC attempts, including the original attempt. - /// - /// This field is required and must be greater than 1. - /// Any value greater than 5 will be treated as if it were 5. - var maxAttempts: UInt32 = 0 - - /// Exponential backoff parameters. The initial retry attempt will occur at - /// random(0, initial_backoff). In general, the nth attempt will occur at - /// random(0, - /// min(initial_backoff*backoff_multiplier**(n-1), max_backoff)). - /// Required. Must be greater than zero. - var initialBackoff: SwiftProtobuf.Google_Protobuf_Duration { - get {return _initialBackoff ?? SwiftProtobuf.Google_Protobuf_Duration()} - set {_initialBackoff = newValue} - } - /// Returns true if `initialBackoff` has been explicitly set. - var hasInitialBackoff: Bool {return self._initialBackoff != nil} - /// Clears the value of `initialBackoff`. Subsequent reads from it will return its default value. - mutating func clearInitialBackoff() {self._initialBackoff = nil} - - /// Required. Must be greater than zero. - var maxBackoff: SwiftProtobuf.Google_Protobuf_Duration { - get {return _maxBackoff ?? SwiftProtobuf.Google_Protobuf_Duration()} - set {_maxBackoff = newValue} - } - /// Returns true if `maxBackoff` has been explicitly set. - var hasMaxBackoff: Bool {return self._maxBackoff != nil} - /// Clears the value of `maxBackoff`. Subsequent reads from it will return its default value. - mutating func clearMaxBackoff() {self._maxBackoff = nil} - - /// Required. Must be greater than zero. - var backoffMultiplier: Float = 0 - - /// The set of status codes which may be retried. - /// - /// This field is required and must be non-empty. - var retryableStatusCodes: [Google_Rpc_Code] = [] - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _initialBackoff: SwiftProtobuf.Google_Protobuf_Duration? = nil - fileprivate var _maxBackoff: SwiftProtobuf.Google_Protobuf_Duration? = nil - } - - /// The hedging policy for outgoing RPCs. Hedged RPCs may execute more than - /// once on the server, so only idempotent methods should specify a hedging - /// policy. - struct HedgingPolicy: 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 hedging policy will send up to max_requests RPCs. - /// This number represents the total number of all attempts, including - /// the original attempt. - /// - /// This field is required and must be greater than 1. - /// Any value greater than 5 will be treated as if it were 5. - var maxAttempts: UInt32 = 0 - - /// The first RPC will be sent immediately, but the max_requests-1 subsequent - /// hedged RPCs will be sent at intervals of every hedging_delay. Set this - /// to 0 to immediately send all max_requests RPCs. - var hedgingDelay: SwiftProtobuf.Google_Protobuf_Duration { - get {return _hedgingDelay ?? SwiftProtobuf.Google_Protobuf_Duration()} - set {_hedgingDelay = newValue} - } - /// Returns true if `hedgingDelay` has been explicitly set. - var hasHedgingDelay: Bool {return self._hedgingDelay != nil} - /// Clears the value of `hedgingDelay`. Subsequent reads from it will return its default value. - mutating func clearHedgingDelay() {self._hedgingDelay = nil} - - /// 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 canceled and - /// the error returned to the client application layer. - /// - /// This field is optional. - var nonFatalStatusCodes: [Google_Rpc_Code] = [] - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _hedgingDelay: SwiftProtobuf.Google_Protobuf_Duration? = nil - } - - init() {} - - fileprivate var _waitForReady: SwiftProtobuf.Google_Protobuf_BoolValue? = nil - fileprivate var _timeout: SwiftProtobuf.Google_Protobuf_Duration? = nil - fileprivate var _maxRequestMessageBytes: SwiftProtobuf.Google_Protobuf_UInt32Value? = nil - fileprivate var _maxResponseMessageBytes: SwiftProtobuf.Google_Protobuf_UInt32Value? = nil -} - -/// Configuration for pick_first LB policy. -struct Grpc_ServiceConfig_PickFirstConfig: 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 set to true, instructs the LB policy to randomly shuffle the list of - /// addresses received from the name resolver before attempting to connect to - /// them. - var shuffleAddressList: Bool = false - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -/// Configuration for round_robin LB policy. -struct Grpc_ServiceConfig_RoundRobinConfig: 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() {} -} - -/// Configuration for weighted_round_robin LB policy. -struct Grpc_ServiceConfig_WeightedRoundRobinLbConfig: 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. - - /// Whether to enable out-of-band utilization reporting collection from - /// the endpoints. By default, per-request utilization reporting is used. - var enableOobLoadReport: SwiftProtobuf.Google_Protobuf_BoolValue { - get {return _enableOobLoadReport ?? SwiftProtobuf.Google_Protobuf_BoolValue()} - set {_enableOobLoadReport = newValue} - } - /// Returns true if `enableOobLoadReport` has been explicitly set. - var hasEnableOobLoadReport: Bool {return self._enableOobLoadReport != nil} - /// Clears the value of `enableOobLoadReport`. Subsequent reads from it will return its default value. - mutating func clearEnableOobLoadReport() {self._enableOobLoadReport = nil} - - /// Load reporting interval to request from the server. Note that the - /// server may not provide reports as frequently as the client requests. - /// Used only when enable_oob_load_report is true. Default is 10 seconds. - var oobReportingPeriod: SwiftProtobuf.Google_Protobuf_Duration { - get {return _oobReportingPeriod ?? SwiftProtobuf.Google_Protobuf_Duration()} - set {_oobReportingPeriod = newValue} - } - /// Returns true if `oobReportingPeriod` has been explicitly set. - var hasOobReportingPeriod: Bool {return self._oobReportingPeriod != nil} - /// Clears the value of `oobReportingPeriod`. Subsequent reads from it will return its default value. - mutating func clearOobReportingPeriod() {self._oobReportingPeriod = nil} - - /// A given endpoint must report load metrics continuously for at least - /// this long before the endpoint weight will be used. This avoids - /// churn when the set of endpoint addresses changes. Takes effect - /// both immediately after we establish a connection to an endpoint and - /// after weight_expiration_period has caused us to stop using the most - /// recent load metrics. Default is 10 seconds. - var blackoutPeriod: SwiftProtobuf.Google_Protobuf_Duration { - get {return _blackoutPeriod ?? SwiftProtobuf.Google_Protobuf_Duration()} - set {_blackoutPeriod = newValue} - } - /// Returns true if `blackoutPeriod` has been explicitly set. - var hasBlackoutPeriod: Bool {return self._blackoutPeriod != nil} - /// Clears the value of `blackoutPeriod`. Subsequent reads from it will return its default value. - mutating func clearBlackoutPeriod() {self._blackoutPeriod = nil} - - /// If a given endpoint has not reported load metrics in this long, - /// then we stop using the reported weight. This ensures that we do - /// not continue to use very stale weights. Once we stop using a stale - /// value, if we later start seeing fresh reports again, the - /// blackout_period applies. Defaults to 3 minutes. - var weightExpirationPeriod: SwiftProtobuf.Google_Protobuf_Duration { - get {return _weightExpirationPeriod ?? SwiftProtobuf.Google_Protobuf_Duration()} - set {_weightExpirationPeriod = newValue} - } - /// Returns true if `weightExpirationPeriod` has been explicitly set. - var hasWeightExpirationPeriod: Bool {return self._weightExpirationPeriod != nil} - /// Clears the value of `weightExpirationPeriod`. Subsequent reads from it will return its default value. - mutating func clearWeightExpirationPeriod() {self._weightExpirationPeriod = nil} - - /// How often endpoint weights are recalculated. Values less than 100ms are - /// capped at 100ms. Default is 1 second. - var weightUpdatePeriod: SwiftProtobuf.Google_Protobuf_Duration { - get {return _weightUpdatePeriod ?? SwiftProtobuf.Google_Protobuf_Duration()} - set {_weightUpdatePeriod = newValue} - } - /// Returns true if `weightUpdatePeriod` has been explicitly set. - var hasWeightUpdatePeriod: Bool {return self._weightUpdatePeriod != nil} - /// Clears the value of `weightUpdatePeriod`. Subsequent reads from it will return its default value. - mutating func clearWeightUpdatePeriod() {self._weightUpdatePeriod = nil} - - /// The multiplier used to adjust endpoint weights with the error rate - /// calculated as eps/qps. Configuration is rejected if this value is negative. - /// Default is 1.0. - var errorUtilizationPenalty: SwiftProtobuf.Google_Protobuf_FloatValue { - get {return _errorUtilizationPenalty ?? SwiftProtobuf.Google_Protobuf_FloatValue()} - set {_errorUtilizationPenalty = newValue} - } - /// Returns true if `errorUtilizationPenalty` has been explicitly set. - var hasErrorUtilizationPenalty: Bool {return self._errorUtilizationPenalty != nil} - /// Clears the value of `errorUtilizationPenalty`. Subsequent reads from it will return its default value. - mutating func clearErrorUtilizationPenalty() {self._errorUtilizationPenalty = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _enableOobLoadReport: SwiftProtobuf.Google_Protobuf_BoolValue? = nil - fileprivate var _oobReportingPeriod: SwiftProtobuf.Google_Protobuf_Duration? = nil - fileprivate var _blackoutPeriod: SwiftProtobuf.Google_Protobuf_Duration? = nil - fileprivate var _weightExpirationPeriod: SwiftProtobuf.Google_Protobuf_Duration? = nil - fileprivate var _weightUpdatePeriod: SwiftProtobuf.Google_Protobuf_Duration? = nil - fileprivate var _errorUtilizationPenalty: SwiftProtobuf.Google_Protobuf_FloatValue? = nil -} - -/// Configuration for outlier_detection LB policy -struct Grpc_ServiceConfig_OutlierDetectionLoadBalancingConfig: 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 time interval between ejection analysis sweeps. This can result in - /// both new ejections as well as addresses being returned to service. Defaults - /// to 10000ms or 10s. - var interval: SwiftProtobuf.Google_Protobuf_Duration { - get {return _interval ?? SwiftProtobuf.Google_Protobuf_Duration()} - set {_interval = newValue} - } - /// Returns true if `interval` has been explicitly set. - var hasInterval: Bool {return self._interval != nil} - /// Clears the value of `interval`. Subsequent reads from it will return its default value. - mutating func clearInterval() {self._interval = nil} - - /// The base time that as address is ejected for. The real time is equal to the - /// base time multiplied by the number of times the address has been ejected. - /// Defaults to 30000ms or 30s. - var baseEjectionTime: SwiftProtobuf.Google_Protobuf_Duration { - get {return _baseEjectionTime ?? SwiftProtobuf.Google_Protobuf_Duration()} - set {_baseEjectionTime = newValue} - } - /// Returns true if `baseEjectionTime` has been explicitly set. - var hasBaseEjectionTime: Bool {return self._baseEjectionTime != nil} - /// Clears the value of `baseEjectionTime`. Subsequent reads from it will return its default value. - mutating func clearBaseEjectionTime() {self._baseEjectionTime = nil} - - /// The maximum time that an address is ejected for. If not specified, the default value (300000ms or 300s) or - /// the base_ejection_time value is applied, whatever is larger. - var maxEjectionTime: SwiftProtobuf.Google_Protobuf_Duration { - get {return _maxEjectionTime ?? SwiftProtobuf.Google_Protobuf_Duration()} - set {_maxEjectionTime = newValue} - } - /// Returns true if `maxEjectionTime` has been explicitly set. - var hasMaxEjectionTime: Bool {return self._maxEjectionTime != nil} - /// Clears the value of `maxEjectionTime`. Subsequent reads from it will return its default value. - mutating func clearMaxEjectionTime() {self._maxEjectionTime = nil} - - /// The maximum % of an address list that can be ejected due to outlier - /// detection. Defaults to 10% but will eject at least one address regardless of the value. - var maxEjectionPercent: SwiftProtobuf.Google_Protobuf_UInt32Value { - get {return _maxEjectionPercent ?? SwiftProtobuf.Google_Protobuf_UInt32Value()} - set {_maxEjectionPercent = newValue} - } - /// Returns true if `maxEjectionPercent` has been explicitly set. - var hasMaxEjectionPercent: Bool {return self._maxEjectionPercent != nil} - /// Clears the value of `maxEjectionPercent`. Subsequent reads from it will return its default value. - mutating func clearMaxEjectionPercent() {self._maxEjectionPercent = nil} - - /// If set, success rate ejections will be performed - var successRateEjection: Grpc_ServiceConfig_OutlierDetectionLoadBalancingConfig.SuccessRateEjection { - get {return _successRateEjection ?? Grpc_ServiceConfig_OutlierDetectionLoadBalancingConfig.SuccessRateEjection()} - set {_successRateEjection = newValue} - } - /// Returns true if `successRateEjection` has been explicitly set. - var hasSuccessRateEjection: Bool {return self._successRateEjection != nil} - /// Clears the value of `successRateEjection`. Subsequent reads from it will return its default value. - mutating func clearSuccessRateEjection() {self._successRateEjection = nil} - - /// If set, failure rate ejections will be performed - var failurePercentageEjection: Grpc_ServiceConfig_OutlierDetectionLoadBalancingConfig.FailurePercentageEjection { - get {return _failurePercentageEjection ?? Grpc_ServiceConfig_OutlierDetectionLoadBalancingConfig.FailurePercentageEjection()} - set {_failurePercentageEjection = newValue} - } - /// Returns true if `failurePercentageEjection` has been explicitly set. - var hasFailurePercentageEjection: Bool {return self._failurePercentageEjection != nil} - /// Clears the value of `failurePercentageEjection`. Subsequent reads from it will return its default value. - mutating func clearFailurePercentageEjection() {self._failurePercentageEjection = nil} - - /// The config for the child policy - var childPolicy: [Grpc_ServiceConfig_LoadBalancingConfig] = [] - - var unknownFields = SwiftProtobuf.UnknownStorage() - - /// Parameters for the success rate ejection algorithm. - /// This algorithm monitors the request success rate for all endpoints and - /// ejects individual endpoints whose success rates are statistical outliers. - struct SuccessRateEjection: 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. - - /// This factor is used to determine the ejection threshold for success rate - /// outlier ejection. The ejection threshold is the difference between the - /// mean success rate, and the product of this factor and the standard - /// deviation of the mean success rate: mean - (stdev * - /// success_rate_stdev_factor). This factor is divided by a thousand to get a - /// double. That is, if the desired factor is 1.9, the runtime value should - /// be 1900. Defaults to 1900. - var stdevFactor: SwiftProtobuf.Google_Protobuf_UInt32Value { - get {return _stdevFactor ?? SwiftProtobuf.Google_Protobuf_UInt32Value()} - set {_stdevFactor = newValue} - } - /// Returns true if `stdevFactor` has been explicitly set. - var hasStdevFactor: Bool {return self._stdevFactor != nil} - /// Clears the value of `stdevFactor`. Subsequent reads from it will return its default value. - mutating func clearStdevFactor() {self._stdevFactor = nil} - - /// The % chance that an address will be actually ejected when an outlier status - /// is detected through success rate statistics. This setting can be used to - /// disable ejection or to ramp it up slowly. Defaults to 100. - var enforcementPercentage: SwiftProtobuf.Google_Protobuf_UInt32Value { - get {return _enforcementPercentage ?? SwiftProtobuf.Google_Protobuf_UInt32Value()} - set {_enforcementPercentage = newValue} - } - /// Returns true if `enforcementPercentage` has been explicitly set. - var hasEnforcementPercentage: Bool {return self._enforcementPercentage != nil} - /// Clears the value of `enforcementPercentage`. Subsequent reads from it will return its default value. - mutating func clearEnforcementPercentage() {self._enforcementPercentage = nil} - - /// The number of addresses that must have enough request volume to - /// detect success rate outliers. If the number of addresses is less than this - /// setting, outlier detection via success rate statistics is not performed - /// for any addresses. Defaults to 5. - var minimumHosts: SwiftProtobuf.Google_Protobuf_UInt32Value { - get {return _minimumHosts ?? SwiftProtobuf.Google_Protobuf_UInt32Value()} - set {_minimumHosts = newValue} - } - /// Returns true if `minimumHosts` has been explicitly set. - var hasMinimumHosts: Bool {return self._minimumHosts != nil} - /// Clears the value of `minimumHosts`. Subsequent reads from it will return its default value. - mutating func clearMinimumHosts() {self._minimumHosts = nil} - - /// The minimum number of total requests that must be collected in one - /// interval (as defined by the interval duration above) to include this address - /// in success rate based outlier detection. If the volume is lower than this - /// setting, outlier detection via success rate statistics is not performed - /// for that address. Defaults to 100. - var requestVolume: SwiftProtobuf.Google_Protobuf_UInt32Value { - get {return _requestVolume ?? SwiftProtobuf.Google_Protobuf_UInt32Value()} - set {_requestVolume = newValue} - } - /// Returns true if `requestVolume` has been explicitly set. - var hasRequestVolume: Bool {return self._requestVolume != nil} - /// Clears the value of `requestVolume`. Subsequent reads from it will return its default value. - mutating func clearRequestVolume() {self._requestVolume = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _stdevFactor: SwiftProtobuf.Google_Protobuf_UInt32Value? = nil - fileprivate var _enforcementPercentage: SwiftProtobuf.Google_Protobuf_UInt32Value? = nil - fileprivate var _minimumHosts: SwiftProtobuf.Google_Protobuf_UInt32Value? = nil - fileprivate var _requestVolume: SwiftProtobuf.Google_Protobuf_UInt32Value? = nil - } - - /// Parameters for the failure percentage algorithm. - /// This algorithm ejects individual endpoints whose failure rate is greater than - /// some threshold, independently of any other endpoint. - struct FailurePercentageEjection: 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 failure percentage to use when determining failure percentage-based outlier detection. If - /// the failure percentage of a given address is greater than or equal to this value, it will be - /// ejected. Defaults to 85. - var threshold: SwiftProtobuf.Google_Protobuf_UInt32Value { - get {return _threshold ?? SwiftProtobuf.Google_Protobuf_UInt32Value()} - set {_threshold = newValue} - } - /// Returns true if `threshold` has been explicitly set. - var hasThreshold: Bool {return self._threshold != nil} - /// Clears the value of `threshold`. Subsequent reads from it will return its default value. - mutating func clearThreshold() {self._threshold = nil} - - /// The % chance that an address will be actually ejected when an outlier status is detected through - /// failure percentage statistics. This setting can be used to disable ejection or to ramp it up - /// slowly. Defaults to 100. - var enforcementPercentage: SwiftProtobuf.Google_Protobuf_UInt32Value { - get {return _enforcementPercentage ?? SwiftProtobuf.Google_Protobuf_UInt32Value()} - set {_enforcementPercentage = newValue} - } - /// Returns true if `enforcementPercentage` has been explicitly set. - var hasEnforcementPercentage: Bool {return self._enforcementPercentage != nil} - /// Clears the value of `enforcementPercentage`. Subsequent reads from it will return its default value. - mutating func clearEnforcementPercentage() {self._enforcementPercentage = nil} - - /// The minimum number of addresses in order to perform failure percentage-based ejection. - /// If the total number of addresses is less than this value, failure percentage-based - /// ejection will not be performed. Defaults to 5. - var minimumHosts: SwiftProtobuf.Google_Protobuf_UInt32Value { - get {return _minimumHosts ?? SwiftProtobuf.Google_Protobuf_UInt32Value()} - set {_minimumHosts = newValue} - } - /// Returns true if `minimumHosts` has been explicitly set. - var hasMinimumHosts: Bool {return self._minimumHosts != nil} - /// Clears the value of `minimumHosts`. Subsequent reads from it will return its default value. - mutating func clearMinimumHosts() {self._minimumHosts = nil} - - /// The minimum number of total requests that must be collected in one interval (as defined by the - /// interval duration above) to perform failure percentage-based ejection for this address. If the - /// volume is lower than this setting, failure percentage-based ejection will not be performed for - /// this host. Defaults to 50. - var requestVolume: SwiftProtobuf.Google_Protobuf_UInt32Value { - get {return _requestVolume ?? SwiftProtobuf.Google_Protobuf_UInt32Value()} - set {_requestVolume = newValue} - } - /// Returns true if `requestVolume` has been explicitly set. - var hasRequestVolume: Bool {return self._requestVolume != nil} - /// Clears the value of `requestVolume`. Subsequent reads from it will return its default value. - mutating func clearRequestVolume() {self._requestVolume = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _threshold: SwiftProtobuf.Google_Protobuf_UInt32Value? = nil - fileprivate var _enforcementPercentage: SwiftProtobuf.Google_Protobuf_UInt32Value? = nil - fileprivate var _minimumHosts: SwiftProtobuf.Google_Protobuf_UInt32Value? = nil - fileprivate var _requestVolume: SwiftProtobuf.Google_Protobuf_UInt32Value? = nil - } - - init() {} - - fileprivate var _interval: SwiftProtobuf.Google_Protobuf_Duration? = nil - fileprivate var _baseEjectionTime: SwiftProtobuf.Google_Protobuf_Duration? = nil - fileprivate var _maxEjectionTime: SwiftProtobuf.Google_Protobuf_Duration? = nil - fileprivate var _maxEjectionPercent: SwiftProtobuf.Google_Protobuf_UInt32Value? = nil - fileprivate var _successRateEjection: Grpc_ServiceConfig_OutlierDetectionLoadBalancingConfig.SuccessRateEjection? = nil - fileprivate var _failurePercentageEjection: Grpc_ServiceConfig_OutlierDetectionLoadBalancingConfig.FailurePercentageEjection? = nil -} - -/// Configuration for grpclb LB policy. -struct Grpc_ServiceConfig_GrpcLbConfig: 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. What LB policy to use for routing between the backend - /// addresses. If unset, defaults to round_robin. - /// Currently, the only supported values are round_robin and pick_first. - /// Note that this will be used both in balancer mode and in fallback mode. - /// Multiple LB policies can be specified; clients will iterate through - /// the list in order and stop at the first policy that they support. - var childPolicy: [Grpc_ServiceConfig_LoadBalancingConfig] = [] - - /// Optional. If specified, overrides the name of the service to be sent to - /// the balancer. - var serviceName: String = String() - - /// Optional. The timeout in seconds for receiving the server list from the LB - /// server. Defaults to 10s. - var initialFallbackTimeout: SwiftProtobuf.Google_Protobuf_Duration { - get {return _initialFallbackTimeout ?? SwiftProtobuf.Google_Protobuf_Duration()} - set {_initialFallbackTimeout = newValue} - } - /// Returns true if `initialFallbackTimeout` has been explicitly set. - var hasInitialFallbackTimeout: Bool {return self._initialFallbackTimeout != nil} - /// Clears the value of `initialFallbackTimeout`. Subsequent reads from it will return its default value. - mutating func clearInitialFallbackTimeout() {self._initialFallbackTimeout = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _initialFallbackTimeout: SwiftProtobuf.Google_Protobuf_Duration? = nil -} - -/// Configuration for priority LB policy. -struct Grpc_ServiceConfig_PriorityLoadBalancingPolicyConfig: 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 children: Dictionary = [:] - - /// A list of child names in decreasing priority order - /// (i.e., first element is the highest priority). - var priorities: [String] = [] - - var unknownFields = SwiftProtobuf.UnknownStorage() - - /// A map of name to child policy configuration. - /// The names are used to allow the priority policy to update - /// existing child policies instead of creating new ones every - /// time it receives a config update. - struct Child: 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 config: [Grpc_ServiceConfig_LoadBalancingConfig] = [] - - /// If true, will ignore reresolution requests from this child. - var ignoreReresolutionRequests: Bool = false - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - } - - init() {} -} - -/// Configuration for weighted_target LB policy. -struct Grpc_ServiceConfig_WeightedTargetLoadBalancingPolicyConfig: 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 targets: Dictionary = [:] - - var unknownFields = SwiftProtobuf.UnknownStorage() - - struct Target: 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 weight: UInt32 = 0 - - var childPolicy: [Grpc_ServiceConfig_LoadBalancingConfig] = [] - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - } - - init() {} -} - -/// Config for RLS LB policy. -struct Grpc_ServiceConfig_RlsLoadBalancingPolicyConfig: @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. - - var routeLookupConfig: Grpc_Lookup_V1_RouteLookupConfig { - get {return _storage._routeLookupConfig ?? Grpc_Lookup_V1_RouteLookupConfig()} - set {_uniqueStorage()._routeLookupConfig = newValue} - } - /// Returns true if `routeLookupConfig` has been explicitly set. - var hasRouteLookupConfig: Bool {return _storage._routeLookupConfig != nil} - /// Clears the value of `routeLookupConfig`. Subsequent reads from it will return its default value. - mutating func clearRouteLookupConfig() {_uniqueStorage()._routeLookupConfig = nil} - - /// Service config to use for the RLS channel. - var routeLookupChannelServiceConfig: Grpc_ServiceConfig_ServiceConfig { - get {return _storage._routeLookupChannelServiceConfig ?? Grpc_ServiceConfig_ServiceConfig()} - set {_uniqueStorage()._routeLookupChannelServiceConfig = newValue} - } - /// Returns true if `routeLookupChannelServiceConfig` has been explicitly set. - var hasRouteLookupChannelServiceConfig: Bool {return _storage._routeLookupChannelServiceConfig != nil} - /// Clears the value of `routeLookupChannelServiceConfig`. Subsequent reads from it will return its default value. - mutating func clearRouteLookupChannelServiceConfig() {_uniqueStorage()._routeLookupChannelServiceConfig = nil} - - var childPolicy: [Grpc_ServiceConfig_LoadBalancingConfig] { - get {return _storage._childPolicy} - set {_uniqueStorage()._childPolicy = newValue} - } - - /// Field name to add to child policy config to contain the target name. - var childPolicyConfigTargetFieldName: String { - get {return _storage._childPolicyConfigTargetFieldName} - set {_uniqueStorage()._childPolicyConfigTargetFieldName = newValue} - } - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _storage = _StorageClass.defaultInstance -} - -/// Configuration for xds_cluster_manager_experimental LB policy. -struct Grpc_ServiceConfig_XdsClusterManagerLoadBalancingPolicyConfig: 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 children: Dictionary = [:] - - var unknownFields = SwiftProtobuf.UnknownStorage() - - struct Child: 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 childPolicy: [Grpc_ServiceConfig_LoadBalancingConfig] = [] - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - } - - init() {} -} - -/// Configuration for the cds LB policy. -struct Grpc_ServiceConfig_CdsConfig: 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. - - /// Required. - var cluster: String = String() - - /// If true, a dynamic subscription will be started for the cluster. - var isDynamic: Bool = false - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -/// Configuration for xds_cluster_impl LB policy. -struct Grpc_ServiceConfig_XdsClusterImplLoadBalancingPolicyConfig: 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. - - /// Cluster name. Required. - var cluster: String = String() - - /// Child policy. - var childPolicy: [Grpc_ServiceConfig_LoadBalancingConfig] = [] - - /// EDS service name. - /// Not set if cluster is not an EDS cluster or if it does not - /// specify an EDS service name. - /// - /// NOTE: This field was marked as deprecated in the .proto file. - var edsServiceName: String = String() - - /// Server to send load reports to. - /// If unset, no load reporting is done. - /// If set to empty string, load reporting will be sent to the same - /// server as we are getting xds data from. - /// DEPRECATED: Use new lrs_load_reporting_server field instead. - /// - /// NOTE: This field was marked as deprecated in the .proto file. - var lrsLoadReportingServerName: SwiftProtobuf.Google_Protobuf_StringValue { - get {return _lrsLoadReportingServerName ?? SwiftProtobuf.Google_Protobuf_StringValue()} - set {_lrsLoadReportingServerName = newValue} - } - /// Returns true if `lrsLoadReportingServerName` has been explicitly set. - var hasLrsLoadReportingServerName: Bool {return self._lrsLoadReportingServerName != nil} - /// Clears the value of `lrsLoadReportingServerName`. Subsequent reads from it will return its default value. - mutating func clearLrsLoadReportingServerName() {self._lrsLoadReportingServerName = nil} - - /// LRS server to send load reports to. - /// If not present, load reporting will be disabled. - /// Supercedes lrs_load_reporting_server_name field. - /// - /// NOTE: This field was marked as deprecated in the .proto file. - var lrsLoadReportingServer: Grpc_ServiceConfig_XdsServer { - get {return _lrsLoadReportingServer ?? Grpc_ServiceConfig_XdsServer()} - set {_lrsLoadReportingServer = newValue} - } - /// Returns true if `lrsLoadReportingServer` has been explicitly set. - var hasLrsLoadReportingServer: Bool {return self._lrsLoadReportingServer != nil} - /// Clears the value of `lrsLoadReportingServer`. Subsequent reads from it will return its default value. - mutating func clearLrsLoadReportingServer() {self._lrsLoadReportingServer = nil} - - /// Maximum number of outstanding requests can be made to the upstream cluster. - /// Default is 1024. - /// - /// NOTE: This field was marked as deprecated in the .proto file. - var maxConcurrentRequests: SwiftProtobuf.Google_Protobuf_UInt32Value { - get {return _maxConcurrentRequests ?? SwiftProtobuf.Google_Protobuf_UInt32Value()} - set {_maxConcurrentRequests = newValue} - } - /// Returns true if `maxConcurrentRequests` has been explicitly set. - var hasMaxConcurrentRequests: Bool {return self._maxConcurrentRequests != nil} - /// Clears the value of `maxConcurrentRequests`. Subsequent reads from it will return its default value. - mutating func clearMaxConcurrentRequests() {self._maxConcurrentRequests = nil} - - /// NOTE: This field was marked as deprecated in the .proto file. - var dropCategories: [Grpc_ServiceConfig_XdsClusterImplLoadBalancingPolicyConfig.DropCategory] = [] - - /// Telemetry labels associated with this cluster - /// - /// NOTE: This field was marked as deprecated in the .proto file. - var telemetryLabels: Dictionary = [:] - - var unknownFields = SwiftProtobuf.UnknownStorage() - - /// Drop configuration. - struct DropCategory: 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 category: String = String() - - var requestsPerMillion: UInt32 = 0 - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - } - - init() {} - - fileprivate var _lrsLoadReportingServerName: SwiftProtobuf.Google_Protobuf_StringValue? = nil - fileprivate var _lrsLoadReportingServer: Grpc_ServiceConfig_XdsServer? = nil - fileprivate var _maxConcurrentRequests: SwiftProtobuf.Google_Protobuf_UInt32Value? = nil -} - -/// Configuration for ring_hash LB policy. -struct Grpc_ServiceConfig_RingHashLoadBalancingConfig: 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. - - /// A client-side option will cap these values to 4096. If either of these - /// values are greater than the client-side cap, they will be treated - /// as the client-side cap value. - var minRingSize: UInt64 = 0 - - /// Optional, defaults to 4096, max 8M. - var maxRingSize: UInt64 = 0 - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -/// Configuration for the xds_wrr_locality load balancing policy. -struct Grpc_ServiceConfig_XdsWrrLocalityLoadBalancingPolicyConfig: 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 childPolicy: [Grpc_ServiceConfig_LoadBalancingConfig] = [] - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -/// Configuration for the least_request LB policy. -struct Grpc_ServiceConfig_LeastRequestLocalityLoadBalancingPolicyConfig: 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 choiceCount: UInt64 = 0 - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -/// Configuration for the xds_override_host LB policy. -struct Grpc_ServiceConfig_OverrideHostLoadBalancingPolicyConfig: 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 clusterName: String = String() - - var childPolicy: [Grpc_ServiceConfig_LoadBalancingConfig] = [] - - /// valid health status for hosts that are considered when using - /// xds_override_host_experimental policy. - /// Default is [UNKNOWN, HEALTHY] - /// - /// NOTE: This field was marked as deprecated in the .proto file. - var overrideHostStatus: [Grpc_ServiceConfig_OverrideHostLoadBalancingPolicyConfig.HealthStatus] = [] - - var unknownFields = SwiftProtobuf.UnknownStorage() - - enum HealthStatus: SwiftProtobuf.Enum, Swift.CaseIterable { - typealias RawValue = Int - case unknown // = 0 - case healthy // = 1 - case draining // = 3 - case UNRECOGNIZED(Int) - - init() { - self = .unknown - } - - init?(rawValue: Int) { - switch rawValue { - case 0: self = .unknown - case 1: self = .healthy - case 3: self = .draining - default: self = .UNRECOGNIZED(rawValue) - } - } - - var rawValue: Int { - switch self { - case .unknown: return 0 - case .healthy: return 1 - case .draining: return 3 - case .UNRECOGNIZED(let i): return i - } - } - - // The compiler won't synthesize support with the UNRECOGNIZED case. - static let allCases: [Grpc_ServiceConfig_OverrideHostLoadBalancingPolicyConfig.HealthStatus] = [ - .unknown, - .healthy, - .draining, - ] - - } - - init() {} -} - -/// Selects LB policy and provides corresponding configuration. -/// -/// In general, all instances of this field should be repeated. Clients will -/// iterate through the list in order and stop at the first policy that they -/// support. This allows the service config to specify custom policies that may -/// not be known to all clients. -/// -/// - If the config for the first supported policy is invalid, the whole service -/// config is invalid. -/// - If the list doesn't contain any supported policy, the whole service config -/// is invalid. -struct Grpc_ServiceConfig_LoadBalancingConfig: 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. - - /// Exactly one LB policy may be configured. - var policy: Grpc_ServiceConfig_LoadBalancingConfig.OneOf_Policy? = nil - - var pickFirst: Grpc_ServiceConfig_PickFirstConfig { - get { - if case .pickFirst(let v)? = policy {return v} - return Grpc_ServiceConfig_PickFirstConfig() - } - set {policy = .pickFirst(newValue)} - } - - var roundRobin: Grpc_ServiceConfig_RoundRobinConfig { - get { - if case .roundRobin(let v)? = policy {return v} - return Grpc_ServiceConfig_RoundRobinConfig() - } - set {policy = .roundRobin(newValue)} - } - - var weightedRoundRobin: Grpc_ServiceConfig_WeightedRoundRobinLbConfig { - get { - if case .weightedRoundRobin(let v)? = policy {return v} - return Grpc_ServiceConfig_WeightedRoundRobinLbConfig() - } - set {policy = .weightedRoundRobin(newValue)} - } - - /// gRPC lookaside load balancing. - /// This will eventually be deprecated by the new xDS-based local - /// balancing policy. - var grpclb: Grpc_ServiceConfig_GrpcLbConfig { - get { - if case .grpclb(let v)? = policy {return v} - return Grpc_ServiceConfig_GrpcLbConfig() - } - set {policy = .grpclb(newValue)} - } - - var priorityExperimental: Grpc_ServiceConfig_PriorityLoadBalancingPolicyConfig { - get { - if case .priorityExperimental(let v)? = policy {return v} - return Grpc_ServiceConfig_PriorityLoadBalancingPolicyConfig() - } - set {policy = .priorityExperimental(newValue)} - } - - var weightedTargetExperimental: Grpc_ServiceConfig_WeightedTargetLoadBalancingPolicyConfig { - get { - if case .weightedTargetExperimental(let v)? = policy {return v} - return Grpc_ServiceConfig_WeightedTargetLoadBalancingPolicyConfig() - } - set {policy = .weightedTargetExperimental(newValue)} - } - - var outlierDetection: Grpc_ServiceConfig_OutlierDetectionLoadBalancingConfig { - get { - if case .outlierDetection(let v)? = policy {return v} - return Grpc_ServiceConfig_OutlierDetectionLoadBalancingConfig() - } - set {policy = .outlierDetection(newValue)} - } - - var rls: Grpc_ServiceConfig_RlsLoadBalancingPolicyConfig { - get { - if case .rls(let v)? = policy {return v} - return Grpc_ServiceConfig_RlsLoadBalancingPolicyConfig() - } - set {policy = .rls(newValue)} - } - - /// xDS-based load balancing. - var xdsClusterManagerExperimental: Grpc_ServiceConfig_XdsClusterManagerLoadBalancingPolicyConfig { - get { - if case .xdsClusterManagerExperimental(let v)? = policy {return v} - return Grpc_ServiceConfig_XdsClusterManagerLoadBalancingPolicyConfig() - } - set {policy = .xdsClusterManagerExperimental(newValue)} - } - - var cdsExperimental: Grpc_ServiceConfig_CdsConfig { - get { - if case .cdsExperimental(let v)? = policy {return v} - return Grpc_ServiceConfig_CdsConfig() - } - set {policy = .cdsExperimental(newValue)} - } - - var xdsClusterImplExperimental: Grpc_ServiceConfig_XdsClusterImplLoadBalancingPolicyConfig { - get { - if case .xdsClusterImplExperimental(let v)? = policy {return v} - return Grpc_ServiceConfig_XdsClusterImplLoadBalancingPolicyConfig() - } - set {policy = .xdsClusterImplExperimental(newValue)} - } - - var overrideHostExperimental: Grpc_ServiceConfig_OverrideHostLoadBalancingPolicyConfig { - get { - if case .overrideHostExperimental(let v)? = policy {return v} - return Grpc_ServiceConfig_OverrideHostLoadBalancingPolicyConfig() - } - set {policy = .overrideHostExperimental(newValue)} - } - - var xdsWrrLocalityExperimental: Grpc_ServiceConfig_XdsWrrLocalityLoadBalancingPolicyConfig { - get { - if case .xdsWrrLocalityExperimental(let v)? = policy {return v} - return Grpc_ServiceConfig_XdsWrrLocalityLoadBalancingPolicyConfig() - } - set {policy = .xdsWrrLocalityExperimental(newValue)} - } - - var ringHashExperimental: Grpc_ServiceConfig_RingHashLoadBalancingConfig { - get { - if case .ringHashExperimental(let v)? = policy {return v} - return Grpc_ServiceConfig_RingHashLoadBalancingConfig() - } - set {policy = .ringHashExperimental(newValue)} - } - - var leastRequestExperimental: Grpc_ServiceConfig_LeastRequestLocalityLoadBalancingPolicyConfig { - get { - if case .leastRequestExperimental(let v)? = policy {return v} - return Grpc_ServiceConfig_LeastRequestLocalityLoadBalancingPolicyConfig() - } - set {policy = .leastRequestExperimental(newValue)} - } - - /// Deprecated xDS-related policies. - /// - /// NOTE: This field was marked as deprecated in the .proto file. - var xdsClusterResolverExperimental: Grpc_ServiceConfig_XdsClusterResolverLoadBalancingPolicyConfig { - get { - if case .xdsClusterResolverExperimental(let v)? = policy {return v} - return Grpc_ServiceConfig_XdsClusterResolverLoadBalancingPolicyConfig() - } - set {policy = .xdsClusterResolverExperimental(newValue)} - } - - /// NOTE: This field was marked as deprecated in the .proto file. - var lrsExperimental: Grpc_ServiceConfig_LrsLoadBalancingPolicyConfig { - get { - if case .lrsExperimental(let v)? = policy {return v} - return Grpc_ServiceConfig_LrsLoadBalancingPolicyConfig() - } - set {policy = .lrsExperimental(newValue)} - } - - /// NOTE: This field was marked as deprecated in the .proto file. - var edsExperimental: Grpc_ServiceConfig_EdsLoadBalancingPolicyConfig { - get { - if case .edsExperimental(let v)? = policy {return v} - return Grpc_ServiceConfig_EdsLoadBalancingPolicyConfig() - } - set {policy = .edsExperimental(newValue)} - } - - /// NOTE: This field was marked as deprecated in the .proto file. - var xds: Grpc_ServiceConfig_XdsConfig { - get { - if case .xds(let v)? = policy {return v} - return Grpc_ServiceConfig_XdsConfig() - } - set {policy = .xds(newValue)} - } - - /// NOTE: This field was marked as deprecated in the .proto file. - var xdsExperimental: Grpc_ServiceConfig_XdsConfig { - get { - if case .xdsExperimental(let v)? = policy {return v} - return Grpc_ServiceConfig_XdsConfig() - } - set {policy = .xdsExperimental(newValue)} - } - - var unknownFields = SwiftProtobuf.UnknownStorage() - - /// Exactly one LB policy may be configured. - enum OneOf_Policy: Equatable, Sendable { - case pickFirst(Grpc_ServiceConfig_PickFirstConfig) - case roundRobin(Grpc_ServiceConfig_RoundRobinConfig) - case weightedRoundRobin(Grpc_ServiceConfig_WeightedRoundRobinLbConfig) - /// gRPC lookaside load balancing. - /// This will eventually be deprecated by the new xDS-based local - /// balancing policy. - case grpclb(Grpc_ServiceConfig_GrpcLbConfig) - case priorityExperimental(Grpc_ServiceConfig_PriorityLoadBalancingPolicyConfig) - case weightedTargetExperimental(Grpc_ServiceConfig_WeightedTargetLoadBalancingPolicyConfig) - case outlierDetection(Grpc_ServiceConfig_OutlierDetectionLoadBalancingConfig) - case rls(Grpc_ServiceConfig_RlsLoadBalancingPolicyConfig) - /// xDS-based load balancing. - case xdsClusterManagerExperimental(Grpc_ServiceConfig_XdsClusterManagerLoadBalancingPolicyConfig) - case cdsExperimental(Grpc_ServiceConfig_CdsConfig) - case xdsClusterImplExperimental(Grpc_ServiceConfig_XdsClusterImplLoadBalancingPolicyConfig) - case overrideHostExperimental(Grpc_ServiceConfig_OverrideHostLoadBalancingPolicyConfig) - case xdsWrrLocalityExperimental(Grpc_ServiceConfig_XdsWrrLocalityLoadBalancingPolicyConfig) - case ringHashExperimental(Grpc_ServiceConfig_RingHashLoadBalancingConfig) - case leastRequestExperimental(Grpc_ServiceConfig_LeastRequestLocalityLoadBalancingPolicyConfig) - /// Deprecated xDS-related policies. - /// - /// NOTE: This field was marked as deprecated in the .proto file. - case xdsClusterResolverExperimental(Grpc_ServiceConfig_XdsClusterResolverLoadBalancingPolicyConfig) - /// NOTE: This field was marked as deprecated in the .proto file. - case lrsExperimental(Grpc_ServiceConfig_LrsLoadBalancingPolicyConfig) - /// NOTE: This field was marked as deprecated in the .proto file. - case edsExperimental(Grpc_ServiceConfig_EdsLoadBalancingPolicyConfig) - /// NOTE: This field was marked as deprecated in the .proto file. - case xds(Grpc_ServiceConfig_XdsConfig) - /// NOTE: This field was marked as deprecated in the .proto file. - case xdsExperimental(Grpc_ServiceConfig_XdsConfig) - - } - - init() {} -} - -/// A ServiceConfig represents information about a service but is not specific to -/// any name resolver. -struct Grpc_ServiceConfig_ServiceConfig: 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. - - /// NOTE: This field was marked as deprecated in the .proto file. - var loadBalancingPolicy: Grpc_ServiceConfig_ServiceConfig.LoadBalancingPolicy = .unspecified - - /// Multiple LB policies can be specified; clients will iterate through - /// the list in order and stop at the first policy that they support. If none - /// are supported, the service config is considered invalid. - var loadBalancingConfig: [Grpc_ServiceConfig_LoadBalancingConfig] = [] - - /// Per-method configuration. - var methodConfig: [Grpc_ServiceConfig_MethodConfig] = [] - - var retryThrottling: Grpc_ServiceConfig_ServiceConfig.RetryThrottlingPolicy { - get {return _retryThrottling ?? Grpc_ServiceConfig_ServiceConfig.RetryThrottlingPolicy()} - set {_retryThrottling = newValue} - } - /// Returns true if `retryThrottling` has been explicitly set. - var hasRetryThrottling: Bool {return self._retryThrottling != nil} - /// Clears the value of `retryThrottling`. Subsequent reads from it will return its default value. - mutating func clearRetryThrottling() {self._retryThrottling = nil} - - var healthCheckConfig: Grpc_ServiceConfig_ServiceConfig.HealthCheckConfig { - get {return _healthCheckConfig ?? Grpc_ServiceConfig_ServiceConfig.HealthCheckConfig()} - set {_healthCheckConfig = newValue} - } - /// Returns true if `healthCheckConfig` has been explicitly set. - var hasHealthCheckConfig: Bool {return self._healthCheckConfig != nil} - /// Clears the value of `healthCheckConfig`. Subsequent reads from it will return its default value. - mutating func clearHealthCheckConfig() {self._healthCheckConfig = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - /// Load balancing policy. - /// - /// Note that load_balancing_policy is deprecated in favor of - /// load_balancing_config; the former will be used only if the latter - /// is unset. - /// - /// If no LB policy is configured here, then the default is pick_first. - /// If the policy name is set via the client API, that value overrides - /// the value specified here. - /// - /// If the deprecated load_balancing_policy field is used, note that if the - /// resolver returns at least one balancer address (as opposed to backend - /// addresses), gRPC will use grpclb (see - /// https://github.com/grpc/grpc/blob/master/doc/load-balancing.md), - /// regardless of what policy is configured here. However, if the resolver - /// returns at least one backend address in addition to the balancer - /// address(es), the client may fall back to the requested policy if it - /// is unable to reach any of the grpclb load balancers. - enum LoadBalancingPolicy: SwiftProtobuf.Enum, Swift.CaseIterable { - typealias RawValue = Int - case unspecified // = 0 - case roundRobin // = 1 - case UNRECOGNIZED(Int) - - init() { - self = .unspecified - } - - init?(rawValue: Int) { - switch rawValue { - case 0: self = .unspecified - case 1: self = .roundRobin - default: self = .UNRECOGNIZED(rawValue) - } - } - - var rawValue: Int { - switch self { - case .unspecified: return 0 - case .roundRobin: return 1 - case .UNRECOGNIZED(let i): return i - } - } - - // The compiler won't synthesize support with the UNRECOGNIZED case. - static let allCases: [Grpc_ServiceConfig_ServiceConfig.LoadBalancingPolicy] = [ - .unspecified, - .roundRobin, - ] - - } - - /// If a RetryThrottlingPolicy 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 max_tokens. 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 token_ratio. - /// - /// 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. - struct RetryThrottlingPolicy: 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 tokens starts at max_tokens. The token_count will always be - /// between 0 and max_tokens. - /// - /// This field is required and must be greater than zero. - var maxTokens: UInt32 = 0 - - /// The amount of tokens to add on each successful RPC. Typically this will - /// be some number between 0 and 1, e.g., 0.1. - /// - /// This field is required and must be greater than zero. Up to 3 decimal - /// places are supported. - var tokenRatio: Float = 0 - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - } - - struct HealthCheckConfig: 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. - - /// Service name to use in the health-checking request. - var serviceName: SwiftProtobuf.Google_Protobuf_StringValue { - get {return _serviceName ?? SwiftProtobuf.Google_Protobuf_StringValue()} - set {_serviceName = newValue} - } - /// Returns true if `serviceName` has been explicitly set. - var hasServiceName: Bool {return self._serviceName != nil} - /// Clears the value of `serviceName`. Subsequent reads from it will return its default value. - mutating func clearServiceName() {self._serviceName = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _serviceName: SwiftProtobuf.Google_Protobuf_StringValue? = nil - } - - init() {} - - fileprivate var _retryThrottling: Grpc_ServiceConfig_ServiceConfig.RetryThrottlingPolicy? = nil - fileprivate var _healthCheckConfig: Grpc_ServiceConfig_ServiceConfig.HealthCheckConfig? = nil -} - -/// Represents an xDS server. -/// Deprecated. -struct Grpc_ServiceConfig_XdsServer: 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. - - /// Required. - var serverUri: String = String() - - /// A list of channel creds to use. The first supported type will be used. - var channelCreds: [Grpc_ServiceConfig_XdsServer.ChannelCredentials] = [] - - /// A repeated list of server features. - var serverFeatures: [SwiftProtobuf.Google_Protobuf_Value] = [] - - var unknownFields = SwiftProtobuf.UnknownStorage() - - struct ChannelCredentials: 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. - - /// Required. - var type: String = String() - - /// Optional JSON config. - var config: SwiftProtobuf.Google_Protobuf_Struct { - get {return _config ?? SwiftProtobuf.Google_Protobuf_Struct()} - set {_config = newValue} - } - /// Returns true if `config` has been explicitly set. - var hasConfig: Bool {return self._config != nil} - /// Clears the value of `config`. Subsequent reads from it will return its default value. - mutating func clearConfig() {self._config = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _config: SwiftProtobuf.Google_Protobuf_Struct? = nil - } - - init() {} -} - -/// Configuration for xds_cluster_resolver LB policy. -/// Deprecated. -struct Grpc_ServiceConfig_XdsClusterResolverLoadBalancingPolicyConfig: 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. - - /// Ordered list of discovery mechanisms. - /// Must have at least one element. - /// Results from each discovery mechanism are concatenated together in - /// successive priorities. - var discoveryMechanisms: [Grpc_ServiceConfig_XdsClusterResolverLoadBalancingPolicyConfig.DiscoveryMechanism] = [] - - /// xDS LB policy. Will be used as the child config of the xds_cluster_impl LB policy. - var xdsLbPolicy: [Grpc_ServiceConfig_LoadBalancingConfig] = [] - - var unknownFields = SwiftProtobuf.UnknownStorage() - - /// Describes a discovery mechanism instance. - /// For EDS or LOGICAL_DNS clusters, there will be exactly one - /// DiscoveryMechanism, which will describe the cluster of the parent - /// CDS policy. - /// For aggregate clusters, there will be one DiscoveryMechanism for each - /// underlying cluster. - struct DiscoveryMechanism: @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. - - /// Cluster name. - var cluster: String { - get {return _storage._cluster} - set {_uniqueStorage()._cluster = newValue} - } - - /// LRS server to send load reports to. - /// If not present, load reporting will be disabled. - /// If set to the empty string, load reporting will be sent to the same - /// server that we obtained CDS data from. - /// DEPRECATED: Use new lrs_load_reporting_server field instead. - /// - /// NOTE: This field was marked as deprecated in the .proto file. - var lrsLoadReportingServerName: SwiftProtobuf.Google_Protobuf_StringValue { - get {return _storage._lrsLoadReportingServerName ?? SwiftProtobuf.Google_Protobuf_StringValue()} - set {_uniqueStorage()._lrsLoadReportingServerName = newValue} - } - /// Returns true if `lrsLoadReportingServerName` has been explicitly set. - var hasLrsLoadReportingServerName: Bool {return _storage._lrsLoadReportingServerName != nil} - /// Clears the value of `lrsLoadReportingServerName`. Subsequent reads from it will return its default value. - mutating func clearLrsLoadReportingServerName() {_uniqueStorage()._lrsLoadReportingServerName = nil} - - /// LRS server to send load reports to. - /// If not present, load reporting will be disabled. - /// Supercedes lrs_load_reporting_server_name field. - var lrsLoadReportingServer: Grpc_ServiceConfig_XdsServer { - get {return _storage._lrsLoadReportingServer ?? Grpc_ServiceConfig_XdsServer()} - set {_uniqueStorage()._lrsLoadReportingServer = newValue} - } - /// Returns true if `lrsLoadReportingServer` has been explicitly set. - var hasLrsLoadReportingServer: Bool {return _storage._lrsLoadReportingServer != nil} - /// Clears the value of `lrsLoadReportingServer`. Subsequent reads from it will return its default value. - mutating func clearLrsLoadReportingServer() {_uniqueStorage()._lrsLoadReportingServer = nil} - - /// Maximum number of outstanding requests can be made to the upstream - /// cluster. Default is 1024. - var maxConcurrentRequests: SwiftProtobuf.Google_Protobuf_UInt32Value { - get {return _storage._maxConcurrentRequests ?? SwiftProtobuf.Google_Protobuf_UInt32Value()} - set {_uniqueStorage()._maxConcurrentRequests = newValue} - } - /// Returns true if `maxConcurrentRequests` has been explicitly set. - var hasMaxConcurrentRequests: Bool {return _storage._maxConcurrentRequests != nil} - /// Clears the value of `maxConcurrentRequests`. Subsequent reads from it will return its default value. - mutating func clearMaxConcurrentRequests() {_uniqueStorage()._maxConcurrentRequests = nil} - - var type: Grpc_ServiceConfig_XdsClusterResolverLoadBalancingPolicyConfig.DiscoveryMechanism.TypeEnum { - get {return _storage._type} - set {_uniqueStorage()._type = newValue} - } - - /// For type EDS only. - /// EDS service name, as returned in CDS. - /// May be unset if not specified in CDS. - var edsServiceName: String { - get {return _storage._edsServiceName} - set {_uniqueStorage()._edsServiceName = newValue} - } - - /// For type LOGICAL_DNS only. - /// DNS name to resolve in "host:port" form. - var dnsHostname: String { - get {return _storage._dnsHostname} - set {_uniqueStorage()._dnsHostname = newValue} - } - - /// The configuration for outlier_detection child policies - /// Within this message, the child_policy field will be ignored - var outlierDetection: Grpc_ServiceConfig_OutlierDetectionLoadBalancingConfig { - get {return _storage._outlierDetection ?? Grpc_ServiceConfig_OutlierDetectionLoadBalancingConfig()} - set {_uniqueStorage()._outlierDetection = newValue} - } - /// Returns true if `outlierDetection` has been explicitly set. - var hasOutlierDetection: Bool {return _storage._outlierDetection != nil} - /// Clears the value of `outlierDetection`. Subsequent reads from it will return its default value. - mutating func clearOutlierDetection() {_uniqueStorage()._outlierDetection = nil} - - /// The configuration for xds_override_host child policy - var overrideHostStatus: [Grpc_ServiceConfig_OverrideHostLoadBalancingPolicyConfig.HealthStatus] { - get {return _storage._overrideHostStatus} - set {_uniqueStorage()._overrideHostStatus = newValue} - } - - /// Telemetry labels associated with this cluster - var telemetryLabels: Dictionary { - get {return _storage._telemetryLabels} - set {_uniqueStorage()._telemetryLabels = newValue} - } - - var unknownFields = SwiftProtobuf.UnknownStorage() - - enum TypeEnum: SwiftProtobuf.Enum, Swift.CaseIterable { - typealias RawValue = Int - case unknown // = 0 - case eds // = 1 - case logicalDns // = 2 - case UNRECOGNIZED(Int) - - init() { - self = .unknown - } - - init?(rawValue: Int) { - switch rawValue { - case 0: self = .unknown - case 1: self = .eds - case 2: self = .logicalDns - default: self = .UNRECOGNIZED(rawValue) - } - } - - var rawValue: Int { - switch self { - case .unknown: return 0 - case .eds: return 1 - case .logicalDns: return 2 - case .UNRECOGNIZED(let i): return i - } - } - - // The compiler won't synthesize support with the UNRECOGNIZED case. - static let allCases: [Grpc_ServiceConfig_XdsClusterResolverLoadBalancingPolicyConfig.DiscoveryMechanism.TypeEnum] = [ - .unknown, - .eds, - .logicalDns, - ] - - } - - init() {} - - fileprivate var _storage = _StorageClass.defaultInstance - } - - init() {} -} - -/// Configuration for lrs LB policy. -/// Deprecated. -struct Grpc_ServiceConfig_LrsLoadBalancingPolicyConfig: 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. - - /// Cluster name. Required. - var clusterName: String = String() - - /// EDS service name, as returned in CDS. - /// May be unset if not specified in CDS. - var edsServiceName: String = String() - - /// Server to send load reports to. Required. - /// If set to empty string, load reporting will be sent to the same - /// server as we are getting xds data from. - var lrsLoadReportingServerName: String = String() - - var locality: Grpc_ServiceConfig_LrsLoadBalancingPolicyConfig.Locality { - get {return _locality ?? Grpc_ServiceConfig_LrsLoadBalancingPolicyConfig.Locality()} - set {_locality = newValue} - } - /// Returns true if `locality` has been explicitly set. - var hasLocality: Bool {return self._locality != nil} - /// Clears the value of `locality`. Subsequent reads from it will return its default value. - mutating func clearLocality() {self._locality = nil} - - /// Endpoint-picking policy. - var childPolicy: [Grpc_ServiceConfig_LoadBalancingConfig] = [] - - var unknownFields = SwiftProtobuf.UnknownStorage() - - /// The locality for which this policy will report load. Required. - struct Locality: 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 region: String = String() - - var zone: String = String() - - var subzone: String = String() - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - } - - init() {} - - fileprivate var _locality: Grpc_ServiceConfig_LrsLoadBalancingPolicyConfig.Locality? = nil -} - -/// Configuration for eds LB policy. -/// Deprecated. -struct Grpc_ServiceConfig_EdsLoadBalancingPolicyConfig: 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. - - /// Cluster name. Required. - var cluster: String = String() - - /// EDS service name, as returned in CDS. - /// May be unset if not specified in CDS. - var edsServiceName: String = String() - - /// Server to send load reports to. - /// If unset, no load reporting is done. - /// If set to empty string, load reporting will be sent to the same - /// server as we are getting xds data from. - var lrsLoadReportingServerName: SwiftProtobuf.Google_Protobuf_StringValue { - get {return _lrsLoadReportingServerName ?? SwiftProtobuf.Google_Protobuf_StringValue()} - set {_lrsLoadReportingServerName = newValue} - } - /// Returns true if `lrsLoadReportingServerName` has been explicitly set. - var hasLrsLoadReportingServerName: Bool {return self._lrsLoadReportingServerName != nil} - /// Clears the value of `lrsLoadReportingServerName`. Subsequent reads from it will return its default value. - mutating func clearLrsLoadReportingServerName() {self._lrsLoadReportingServerName = nil} - - /// Locality-picking policy. - /// This policy's config is expected to be in the format used - /// by the weighted_target policy. Note that the config should include - /// an empty value for the "targets" field; that empty value will be - /// replaced by one that is dynamically generated based on the EDS data. - /// Optional; defaults to "weighted_target". - var localityPickingPolicy: [Grpc_ServiceConfig_LoadBalancingConfig] = [] - - /// Endpoint-picking policy. - /// This will be configured as the policy for each child in the - /// locality-policy's config. - /// Optional; defaults to "round_robin". - var endpointPickingPolicy: [Grpc_ServiceConfig_LoadBalancingConfig] = [] - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _lrsLoadReportingServerName: SwiftProtobuf.Google_Protobuf_StringValue? = nil -} - -/// Configuration for xds LB policy. -/// Deprecated. -struct Grpc_ServiceConfig_XdsConfig: 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. - - /// Name of balancer to connect to. - /// - /// NOTE: This field was marked as deprecated in the .proto file. - var balancerName: String = String() - - /// Optional. What LB policy to use for intra-locality routing. - /// If unset, will use whatever algorithm is specified by the balancer. - /// Multiple LB policies can be specified; clients will iterate through - /// the list in order and stop at the first policy that they support. - var childPolicy: [Grpc_ServiceConfig_LoadBalancingConfig] = [] - - /// Optional. What LB policy to use in fallback mode. If not - /// specified, defaults to round_robin. - /// Multiple LB policies can be specified; clients will iterate through - /// the list in order and stop at the first policy that they support. - var fallbackPolicy: [Grpc_ServiceConfig_LoadBalancingConfig] = [] - - /// Optional. Name to use in EDS query. If not present, defaults to - /// the server name from the target URI. - var edsServiceName: String = String() - - /// LRS server to send load reports to. - /// If not present, load reporting will be disabled. - /// If set to the empty string, load reporting will be sent to the same - /// server that we obtained CDS data from. - var lrsLoadReportingServerName: SwiftProtobuf.Google_Protobuf_StringValue { - get {return _lrsLoadReportingServerName ?? SwiftProtobuf.Google_Protobuf_StringValue()} - set {_lrsLoadReportingServerName = newValue} - } - /// Returns true if `lrsLoadReportingServerName` has been explicitly set. - var hasLrsLoadReportingServerName: Bool {return self._lrsLoadReportingServerName != nil} - /// Clears the value of `lrsLoadReportingServerName`. Subsequent reads from it will return its default value. - mutating func clearLrsLoadReportingServerName() {self._lrsLoadReportingServerName = nil} - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _lrsLoadReportingServerName: SwiftProtobuf.Google_Protobuf_StringValue? = nil -} - -// MARK: - Code below here is support for the SwiftProtobuf runtime. - -fileprivate let _protobuf_package = "grpc.service_config" - -extension Grpc_ServiceConfig_MethodConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".MethodConfig" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "name"), - 2: .standard(proto: "wait_for_ready"), - 3: .same(proto: "timeout"), - 4: .standard(proto: "max_request_message_bytes"), - 5: .standard(proto: "max_response_message_bytes"), - 6: .standard(proto: "retry_policy"), - 7: .standard(proto: "hedging_policy"), - ] - - 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.name) }() - case 2: try { try decoder.decodeSingularMessageField(value: &self._waitForReady) }() - case 3: try { try decoder.decodeSingularMessageField(value: &self._timeout) }() - case 4: try { try decoder.decodeSingularMessageField(value: &self._maxRequestMessageBytes) }() - case 5: try { try decoder.decodeSingularMessageField(value: &self._maxResponseMessageBytes) }() - case 6: try { - var v: Grpc_ServiceConfig_MethodConfig.RetryPolicy? - var hadOneofValue = false - if let current = self.retryOrHedgingPolicy { - hadOneofValue = true - if case .retryPolicy(let m) = current {v = m} - } - try decoder.decodeSingularMessageField(value: &v) - if let v = v { - if hadOneofValue {try decoder.handleConflictingOneOf()} - self.retryOrHedgingPolicy = .retryPolicy(v) - } - }() - case 7: try { - var v: Grpc_ServiceConfig_MethodConfig.HedgingPolicy? - var hadOneofValue = false - if let current = self.retryOrHedgingPolicy { - hadOneofValue = true - if case .hedgingPolicy(let m) = current {v = m} - } - try decoder.decodeSingularMessageField(value: &v) - if let v = v { - if hadOneofValue {try decoder.handleConflictingOneOf()} - self.retryOrHedgingPolicy = .hedgingPolicy(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.visitRepeatedMessageField(value: self.name, fieldNumber: 1) - } - try { if let v = self._waitForReady { - try visitor.visitSingularMessageField(value: v, fieldNumber: 2) - } }() - try { if let v = self._timeout { - try visitor.visitSingularMessageField(value: v, fieldNumber: 3) - } }() - try { if let v = self._maxRequestMessageBytes { - try visitor.visitSingularMessageField(value: v, fieldNumber: 4) - } }() - try { if let v = self._maxResponseMessageBytes { - try visitor.visitSingularMessageField(value: v, fieldNumber: 5) - } }() - switch self.retryOrHedgingPolicy { - case .retryPolicy?: try { - guard case .retryPolicy(let v)? = self.retryOrHedgingPolicy else { preconditionFailure() } - try visitor.visitSingularMessageField(value: v, fieldNumber: 6) - }() - case .hedgingPolicy?: try { - guard case .hedgingPolicy(let v)? = self.retryOrHedgingPolicy else { preconditionFailure() } - try visitor.visitSingularMessageField(value: v, fieldNumber: 7) - }() - case nil: break - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_ServiceConfig_MethodConfig, rhs: Grpc_ServiceConfig_MethodConfig) -> Bool { - if lhs.name != rhs.name {return false} - if lhs._waitForReady != rhs._waitForReady {return false} - if lhs._timeout != rhs._timeout {return false} - if lhs._maxRequestMessageBytes != rhs._maxRequestMessageBytes {return false} - if lhs._maxResponseMessageBytes != rhs._maxResponseMessageBytes {return false} - if lhs.retryOrHedgingPolicy != rhs.retryOrHedgingPolicy {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_ServiceConfig_MethodConfig.Name: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = Grpc_ServiceConfig_MethodConfig.protoMessageName + ".Name" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "service"), - 2: .same(proto: "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.decodeSingularStringField(value: &self.service) }() - case 2: try { try decoder.decodeSingularStringField(value: &self.method) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if !self.service.isEmpty { - try visitor.visitSingularStringField(value: self.service, fieldNumber: 1) - } - if !self.method.isEmpty { - try visitor.visitSingularStringField(value: self.method, fieldNumber: 2) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_ServiceConfig_MethodConfig.Name, rhs: Grpc_ServiceConfig_MethodConfig.Name) -> Bool { - if lhs.service != rhs.service {return false} - if lhs.method != rhs.method {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_ServiceConfig_MethodConfig.RetryPolicy: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = Grpc_ServiceConfig_MethodConfig.protoMessageName + ".RetryPolicy" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "max_attempts"), - 2: .standard(proto: "initial_backoff"), - 3: .standard(proto: "max_backoff"), - 4: .standard(proto: "backoff_multiplier"), - 5: .standard(proto: "retryable_status_codes"), - ] - - 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.decodeSingularUInt32Field(value: &self.maxAttempts) }() - case 2: try { try decoder.decodeSingularMessageField(value: &self._initialBackoff) }() - case 3: try { try decoder.decodeSingularMessageField(value: &self._maxBackoff) }() - case 4: try { try decoder.decodeSingularFloatField(value: &self.backoffMultiplier) }() - case 5: try { try decoder.decodeRepeatedEnumField(value: &self.retryableStatusCodes) }() - 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.maxAttempts != 0 { - try visitor.visitSingularUInt32Field(value: self.maxAttempts, fieldNumber: 1) - } - try { if let v = self._initialBackoff { - try visitor.visitSingularMessageField(value: v, fieldNumber: 2) - } }() - try { if let v = self._maxBackoff { - try visitor.visitSingularMessageField(value: v, fieldNumber: 3) - } }() - if self.backoffMultiplier.bitPattern != 0 { - try visitor.visitSingularFloatField(value: self.backoffMultiplier, fieldNumber: 4) - } - if !self.retryableStatusCodes.isEmpty { - try visitor.visitPackedEnumField(value: self.retryableStatusCodes, fieldNumber: 5) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_ServiceConfig_MethodConfig.RetryPolicy, rhs: Grpc_ServiceConfig_MethodConfig.RetryPolicy) -> Bool { - if lhs.maxAttempts != rhs.maxAttempts {return false} - if lhs._initialBackoff != rhs._initialBackoff {return false} - if lhs._maxBackoff != rhs._maxBackoff {return false} - if lhs.backoffMultiplier != rhs.backoffMultiplier {return false} - if lhs.retryableStatusCodes != rhs.retryableStatusCodes {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_ServiceConfig_MethodConfig.HedgingPolicy: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = Grpc_ServiceConfig_MethodConfig.protoMessageName + ".HedgingPolicy" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "max_attempts"), - 2: .standard(proto: "hedging_delay"), - 3: .standard(proto: "non_fatal_status_codes"), - ] - - 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.decodeSingularUInt32Field(value: &self.maxAttempts) }() - case 2: try { try decoder.decodeSingularMessageField(value: &self._hedgingDelay) }() - case 3: try { try decoder.decodeRepeatedEnumField(value: &self.nonFatalStatusCodes) }() - 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.maxAttempts != 0 { - try visitor.visitSingularUInt32Field(value: self.maxAttempts, fieldNumber: 1) - } - try { if let v = self._hedgingDelay { - try visitor.visitSingularMessageField(value: v, fieldNumber: 2) - } }() - if !self.nonFatalStatusCodes.isEmpty { - try visitor.visitPackedEnumField(value: self.nonFatalStatusCodes, fieldNumber: 3) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_ServiceConfig_MethodConfig.HedgingPolicy, rhs: Grpc_ServiceConfig_MethodConfig.HedgingPolicy) -> Bool { - if lhs.maxAttempts != rhs.maxAttempts {return false} - if lhs._hedgingDelay != rhs._hedgingDelay {return false} - if lhs.nonFatalStatusCodes != rhs.nonFatalStatusCodes {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_ServiceConfig_PickFirstConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".PickFirstConfig" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "shuffle_address_list"), - ] - - 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.shuffleAddressList) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if self.shuffleAddressList != false { - try visitor.visitSingularBoolField(value: self.shuffleAddressList, fieldNumber: 1) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_ServiceConfig_PickFirstConfig, rhs: Grpc_ServiceConfig_PickFirstConfig) -> Bool { - if lhs.shuffleAddressList != rhs.shuffleAddressList {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_ServiceConfig_RoundRobinConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".RoundRobinConfig" - 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_ServiceConfig_RoundRobinConfig, rhs: Grpc_ServiceConfig_RoundRobinConfig) -> Bool { - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_ServiceConfig_WeightedRoundRobinLbConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".WeightedRoundRobinLbConfig" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "enable_oob_load_report"), - 2: .standard(proto: "oob_reporting_period"), - 3: .standard(proto: "blackout_period"), - 4: .standard(proto: "weight_expiration_period"), - 5: .standard(proto: "weight_update_period"), - 6: .standard(proto: "error_utilization_penalty"), - ] - - 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._enableOobLoadReport) }() - case 2: try { try decoder.decodeSingularMessageField(value: &self._oobReportingPeriod) }() - case 3: try { try decoder.decodeSingularMessageField(value: &self._blackoutPeriod) }() - case 4: try { try decoder.decodeSingularMessageField(value: &self._weightExpirationPeriod) }() - case 5: try { try decoder.decodeSingularMessageField(value: &self._weightUpdatePeriod) }() - case 6: try { try decoder.decodeSingularMessageField(value: &self._errorUtilizationPenalty) }() - 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._enableOobLoadReport { - try visitor.visitSingularMessageField(value: v, fieldNumber: 1) - } }() - try { if let v = self._oobReportingPeriod { - try visitor.visitSingularMessageField(value: v, fieldNumber: 2) - } }() - try { if let v = self._blackoutPeriod { - try visitor.visitSingularMessageField(value: v, fieldNumber: 3) - } }() - try { if let v = self._weightExpirationPeriod { - try visitor.visitSingularMessageField(value: v, fieldNumber: 4) - } }() - try { if let v = self._weightUpdatePeriod { - try visitor.visitSingularMessageField(value: v, fieldNumber: 5) - } }() - try { if let v = self._errorUtilizationPenalty { - try visitor.visitSingularMessageField(value: v, fieldNumber: 6) - } }() - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_ServiceConfig_WeightedRoundRobinLbConfig, rhs: Grpc_ServiceConfig_WeightedRoundRobinLbConfig) -> Bool { - if lhs._enableOobLoadReport != rhs._enableOobLoadReport {return false} - if lhs._oobReportingPeriod != rhs._oobReportingPeriod {return false} - if lhs._blackoutPeriod != rhs._blackoutPeriod {return false} - if lhs._weightExpirationPeriod != rhs._weightExpirationPeriod {return false} - if lhs._weightUpdatePeriod != rhs._weightUpdatePeriod {return false} - if lhs._errorUtilizationPenalty != rhs._errorUtilizationPenalty {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_ServiceConfig_OutlierDetectionLoadBalancingConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".OutlierDetectionLoadBalancingConfig" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "interval"), - 2: .standard(proto: "base_ejection_time"), - 3: .standard(proto: "max_ejection_time"), - 4: .standard(proto: "max_ejection_percent"), - 5: .standard(proto: "success_rate_ejection"), - 6: .standard(proto: "failure_percentage_ejection"), - 13: .standard(proto: "child_policy"), - ] - - 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._interval) }() - case 2: try { try decoder.decodeSingularMessageField(value: &self._baseEjectionTime) }() - case 3: try { try decoder.decodeSingularMessageField(value: &self._maxEjectionTime) }() - case 4: try { try decoder.decodeSingularMessageField(value: &self._maxEjectionPercent) }() - case 5: try { try decoder.decodeSingularMessageField(value: &self._successRateEjection) }() - case 6: try { try decoder.decodeSingularMessageField(value: &self._failurePercentageEjection) }() - case 13: try { try decoder.decodeRepeatedMessageField(value: &self.childPolicy) }() - 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._interval { - try visitor.visitSingularMessageField(value: v, fieldNumber: 1) - } }() - try { if let v = self._baseEjectionTime { - try visitor.visitSingularMessageField(value: v, fieldNumber: 2) - } }() - try { if let v = self._maxEjectionTime { - try visitor.visitSingularMessageField(value: v, fieldNumber: 3) - } }() - try { if let v = self._maxEjectionPercent { - try visitor.visitSingularMessageField(value: v, fieldNumber: 4) - } }() - try { if let v = self._successRateEjection { - try visitor.visitSingularMessageField(value: v, fieldNumber: 5) - } }() - try { if let v = self._failurePercentageEjection { - try visitor.visitSingularMessageField(value: v, fieldNumber: 6) - } }() - if !self.childPolicy.isEmpty { - try visitor.visitRepeatedMessageField(value: self.childPolicy, fieldNumber: 13) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_ServiceConfig_OutlierDetectionLoadBalancingConfig, rhs: Grpc_ServiceConfig_OutlierDetectionLoadBalancingConfig) -> Bool { - if lhs._interval != rhs._interval {return false} - if lhs._baseEjectionTime != rhs._baseEjectionTime {return false} - if lhs._maxEjectionTime != rhs._maxEjectionTime {return false} - if lhs._maxEjectionPercent != rhs._maxEjectionPercent {return false} - if lhs._successRateEjection != rhs._successRateEjection {return false} - if lhs._failurePercentageEjection != rhs._failurePercentageEjection {return false} - if lhs.childPolicy != rhs.childPolicy {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_ServiceConfig_OutlierDetectionLoadBalancingConfig.SuccessRateEjection: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = Grpc_ServiceConfig_OutlierDetectionLoadBalancingConfig.protoMessageName + ".SuccessRateEjection" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "stdev_factor"), - 2: .standard(proto: "enforcement_percentage"), - 3: .standard(proto: "minimum_hosts"), - 4: .standard(proto: "request_volume"), - ] - - 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._stdevFactor) }() - case 2: try { try decoder.decodeSingularMessageField(value: &self._enforcementPercentage) }() - case 3: try { try decoder.decodeSingularMessageField(value: &self._minimumHosts) }() - case 4: try { try decoder.decodeSingularMessageField(value: &self._requestVolume) }() - 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._stdevFactor { - try visitor.visitSingularMessageField(value: v, fieldNumber: 1) - } }() - try { if let v = self._enforcementPercentage { - try visitor.visitSingularMessageField(value: v, fieldNumber: 2) - } }() - try { if let v = self._minimumHosts { - try visitor.visitSingularMessageField(value: v, fieldNumber: 3) - } }() - try { if let v = self._requestVolume { - try visitor.visitSingularMessageField(value: v, fieldNumber: 4) - } }() - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_ServiceConfig_OutlierDetectionLoadBalancingConfig.SuccessRateEjection, rhs: Grpc_ServiceConfig_OutlierDetectionLoadBalancingConfig.SuccessRateEjection) -> Bool { - if lhs._stdevFactor != rhs._stdevFactor {return false} - if lhs._enforcementPercentage != rhs._enforcementPercentage {return false} - if lhs._minimumHosts != rhs._minimumHosts {return false} - if lhs._requestVolume != rhs._requestVolume {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_ServiceConfig_OutlierDetectionLoadBalancingConfig.FailurePercentageEjection: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = Grpc_ServiceConfig_OutlierDetectionLoadBalancingConfig.protoMessageName + ".FailurePercentageEjection" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "threshold"), - 2: .standard(proto: "enforcement_percentage"), - 3: .standard(proto: "minimum_hosts"), - 4: .standard(proto: "request_volume"), - ] - - 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._threshold) }() - case 2: try { try decoder.decodeSingularMessageField(value: &self._enforcementPercentage) }() - case 3: try { try decoder.decodeSingularMessageField(value: &self._minimumHosts) }() - case 4: try { try decoder.decodeSingularMessageField(value: &self._requestVolume) }() - 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._threshold { - try visitor.visitSingularMessageField(value: v, fieldNumber: 1) - } }() - try { if let v = self._enforcementPercentage { - try visitor.visitSingularMessageField(value: v, fieldNumber: 2) - } }() - try { if let v = self._minimumHosts { - try visitor.visitSingularMessageField(value: v, fieldNumber: 3) - } }() - try { if let v = self._requestVolume { - try visitor.visitSingularMessageField(value: v, fieldNumber: 4) - } }() - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_ServiceConfig_OutlierDetectionLoadBalancingConfig.FailurePercentageEjection, rhs: Grpc_ServiceConfig_OutlierDetectionLoadBalancingConfig.FailurePercentageEjection) -> Bool { - if lhs._threshold != rhs._threshold {return false} - if lhs._enforcementPercentage != rhs._enforcementPercentage {return false} - if lhs._minimumHosts != rhs._minimumHosts {return false} - if lhs._requestVolume != rhs._requestVolume {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_ServiceConfig_GrpcLbConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".GrpcLbConfig" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "child_policy"), - 2: .standard(proto: "service_name"), - 3: .standard(proto: "initial_fallback_timeout"), - ] - - 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.childPolicy) }() - case 2: try { try decoder.decodeSingularStringField(value: &self.serviceName) }() - case 3: try { try decoder.decodeSingularMessageField(value: &self._initialFallbackTimeout) }() - 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.childPolicy.isEmpty { - try visitor.visitRepeatedMessageField(value: self.childPolicy, fieldNumber: 1) - } - if !self.serviceName.isEmpty { - try visitor.visitSingularStringField(value: self.serviceName, fieldNumber: 2) - } - try { if let v = self._initialFallbackTimeout { - try visitor.visitSingularMessageField(value: v, fieldNumber: 3) - } }() - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_ServiceConfig_GrpcLbConfig, rhs: Grpc_ServiceConfig_GrpcLbConfig) -> Bool { - if lhs.childPolicy != rhs.childPolicy {return false} - if lhs.serviceName != rhs.serviceName {return false} - if lhs._initialFallbackTimeout != rhs._initialFallbackTimeout {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_ServiceConfig_PriorityLoadBalancingPolicyConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".PriorityLoadBalancingPolicyConfig" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "children"), - 2: .same(proto: "priorities"), - ] - - 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._ProtobufMessageMap.self, value: &self.children) }() - case 2: try { try decoder.decodeRepeatedStringField(value: &self.priorities) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if !self.children.isEmpty { - try visitor.visitMapField(fieldType: SwiftProtobuf._ProtobufMessageMap.self, value: self.children, fieldNumber: 1) - } - if !self.priorities.isEmpty { - try visitor.visitRepeatedStringField(value: self.priorities, fieldNumber: 2) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_ServiceConfig_PriorityLoadBalancingPolicyConfig, rhs: Grpc_ServiceConfig_PriorityLoadBalancingPolicyConfig) -> Bool { - if lhs.children != rhs.children {return false} - if lhs.priorities != rhs.priorities {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_ServiceConfig_PriorityLoadBalancingPolicyConfig.Child: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = Grpc_ServiceConfig_PriorityLoadBalancingPolicyConfig.protoMessageName + ".Child" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "config"), - 2: .standard(proto: "ignore_reresolution_requests"), - ] - - 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.config) }() - case 2: try { try decoder.decodeSingularBoolField(value: &self.ignoreReresolutionRequests) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if !self.config.isEmpty { - try visitor.visitRepeatedMessageField(value: self.config, fieldNumber: 1) - } - if self.ignoreReresolutionRequests != false { - try visitor.visitSingularBoolField(value: self.ignoreReresolutionRequests, fieldNumber: 2) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_ServiceConfig_PriorityLoadBalancingPolicyConfig.Child, rhs: Grpc_ServiceConfig_PriorityLoadBalancingPolicyConfig.Child) -> Bool { - if lhs.config != rhs.config {return false} - if lhs.ignoreReresolutionRequests != rhs.ignoreReresolutionRequests {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_ServiceConfig_WeightedTargetLoadBalancingPolicyConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".WeightedTargetLoadBalancingPolicyConfig" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "targets"), - ] - - 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._ProtobufMessageMap.self, value: &self.targets) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if !self.targets.isEmpty { - try visitor.visitMapField(fieldType: SwiftProtobuf._ProtobufMessageMap.self, value: self.targets, fieldNumber: 1) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_ServiceConfig_WeightedTargetLoadBalancingPolicyConfig, rhs: Grpc_ServiceConfig_WeightedTargetLoadBalancingPolicyConfig) -> Bool { - if lhs.targets != rhs.targets {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_ServiceConfig_WeightedTargetLoadBalancingPolicyConfig.Target: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = Grpc_ServiceConfig_WeightedTargetLoadBalancingPolicyConfig.protoMessageName + ".Target" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "weight"), - 2: .standard(proto: "child_policy"), - ] - - 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.decodeSingularUInt32Field(value: &self.weight) }() - case 2: try { try decoder.decodeRepeatedMessageField(value: &self.childPolicy) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if self.weight != 0 { - try visitor.visitSingularUInt32Field(value: self.weight, fieldNumber: 1) - } - if !self.childPolicy.isEmpty { - try visitor.visitRepeatedMessageField(value: self.childPolicy, fieldNumber: 2) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_ServiceConfig_WeightedTargetLoadBalancingPolicyConfig.Target, rhs: Grpc_ServiceConfig_WeightedTargetLoadBalancingPolicyConfig.Target) -> Bool { - if lhs.weight != rhs.weight {return false} - if lhs.childPolicy != rhs.childPolicy {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_ServiceConfig_RlsLoadBalancingPolicyConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".RlsLoadBalancingPolicyConfig" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "route_lookup_config"), - 2: .standard(proto: "route_lookup_channel_service_config"), - 3: .standard(proto: "child_policy"), - 4: .standard(proto: "child_policy_config_target_field_name"), - ] - - fileprivate class _StorageClass { - var _routeLookupConfig: Grpc_Lookup_V1_RouteLookupConfig? = nil - var _routeLookupChannelServiceConfig: Grpc_ServiceConfig_ServiceConfig? = nil - var _childPolicy: [Grpc_ServiceConfig_LoadBalancingConfig] = [] - var _childPolicyConfigTargetFieldName: String = String() - - #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) { - _routeLookupConfig = source._routeLookupConfig - _routeLookupChannelServiceConfig = source._routeLookupChannelServiceConfig - _childPolicy = source._childPolicy - _childPolicyConfigTargetFieldName = source._childPolicyConfigTargetFieldName - } - } - - 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.decodeSingularMessageField(value: &_storage._routeLookupConfig) }() - case 2: try { try decoder.decodeSingularMessageField(value: &_storage._routeLookupChannelServiceConfig) }() - case 3: try { try decoder.decodeRepeatedMessageField(value: &_storage._childPolicy) }() - case 4: try { try decoder.decodeSingularStringField(value: &_storage._childPolicyConfigTargetFieldName) }() - 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 - try { if let v = _storage._routeLookupConfig { - try visitor.visitSingularMessageField(value: v, fieldNumber: 1) - } }() - try { if let v = _storage._routeLookupChannelServiceConfig { - try visitor.visitSingularMessageField(value: v, fieldNumber: 2) - } }() - if !_storage._childPolicy.isEmpty { - try visitor.visitRepeatedMessageField(value: _storage._childPolicy, fieldNumber: 3) - } - if !_storage._childPolicyConfigTargetFieldName.isEmpty { - try visitor.visitSingularStringField(value: _storage._childPolicyConfigTargetFieldName, fieldNumber: 4) - } - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_ServiceConfig_RlsLoadBalancingPolicyConfig, rhs: Grpc_ServiceConfig_RlsLoadBalancingPolicyConfig) -> 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._routeLookupConfig != rhs_storage._routeLookupConfig {return false} - if _storage._routeLookupChannelServiceConfig != rhs_storage._routeLookupChannelServiceConfig {return false} - if _storage._childPolicy != rhs_storage._childPolicy {return false} - if _storage._childPolicyConfigTargetFieldName != rhs_storage._childPolicyConfigTargetFieldName {return false} - return true - } - if !storagesAreEqual {return false} - } - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_ServiceConfig_XdsClusterManagerLoadBalancingPolicyConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".XdsClusterManagerLoadBalancingPolicyConfig" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "children"), - ] - - 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._ProtobufMessageMap.self, value: &self.children) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if !self.children.isEmpty { - try visitor.visitMapField(fieldType: SwiftProtobuf._ProtobufMessageMap.self, value: self.children, fieldNumber: 1) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_ServiceConfig_XdsClusterManagerLoadBalancingPolicyConfig, rhs: Grpc_ServiceConfig_XdsClusterManagerLoadBalancingPolicyConfig) -> Bool { - if lhs.children != rhs.children {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_ServiceConfig_XdsClusterManagerLoadBalancingPolicyConfig.Child: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = Grpc_ServiceConfig_XdsClusterManagerLoadBalancingPolicyConfig.protoMessageName + ".Child" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "child_policy"), - ] - - 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.childPolicy) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if !self.childPolicy.isEmpty { - try visitor.visitRepeatedMessageField(value: self.childPolicy, fieldNumber: 1) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_ServiceConfig_XdsClusterManagerLoadBalancingPolicyConfig.Child, rhs: Grpc_ServiceConfig_XdsClusterManagerLoadBalancingPolicyConfig.Child) -> Bool { - if lhs.childPolicy != rhs.childPolicy {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_ServiceConfig_CdsConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".CdsConfig" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "cluster"), - 2: .standard(proto: "is_dynamic"), - ] - - 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.cluster) }() - case 2: try { try decoder.decodeSingularBoolField(value: &self.isDynamic) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if !self.cluster.isEmpty { - try visitor.visitSingularStringField(value: self.cluster, fieldNumber: 1) - } - if self.isDynamic != false { - try visitor.visitSingularBoolField(value: self.isDynamic, fieldNumber: 2) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_ServiceConfig_CdsConfig, rhs: Grpc_ServiceConfig_CdsConfig) -> Bool { - if lhs.cluster != rhs.cluster {return false} - if lhs.isDynamic != rhs.isDynamic {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_ServiceConfig_XdsClusterImplLoadBalancingPolicyConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".XdsClusterImplLoadBalancingPolicyConfig" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "cluster"), - 6: .standard(proto: "child_policy"), - 2: .standard(proto: "eds_service_name"), - 3: .standard(proto: "lrs_load_reporting_server_name"), - 7: .standard(proto: "lrs_load_reporting_server"), - 4: .standard(proto: "max_concurrent_requests"), - 5: .standard(proto: "drop_categories"), - 8: .standard(proto: "telemetry_labels"), - ] - - 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.cluster) }() - case 2: try { try decoder.decodeSingularStringField(value: &self.edsServiceName) }() - case 3: try { try decoder.decodeSingularMessageField(value: &self._lrsLoadReportingServerName) }() - case 4: try { try decoder.decodeSingularMessageField(value: &self._maxConcurrentRequests) }() - case 5: try { try decoder.decodeRepeatedMessageField(value: &self.dropCategories) }() - case 6: try { try decoder.decodeRepeatedMessageField(value: &self.childPolicy) }() - case 7: try { try decoder.decodeSingularMessageField(value: &self._lrsLoadReportingServer) }() - case 8: try { try decoder.decodeMapField(fieldType: SwiftProtobuf._ProtobufMap.self, value: &self.telemetryLabels) }() - 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.cluster.isEmpty { - try visitor.visitSingularStringField(value: self.cluster, fieldNumber: 1) - } - if !self.edsServiceName.isEmpty { - try visitor.visitSingularStringField(value: self.edsServiceName, fieldNumber: 2) - } - try { if let v = self._lrsLoadReportingServerName { - try visitor.visitSingularMessageField(value: v, fieldNumber: 3) - } }() - try { if let v = self._maxConcurrentRequests { - try visitor.visitSingularMessageField(value: v, fieldNumber: 4) - } }() - if !self.dropCategories.isEmpty { - try visitor.visitRepeatedMessageField(value: self.dropCategories, fieldNumber: 5) - } - if !self.childPolicy.isEmpty { - try visitor.visitRepeatedMessageField(value: self.childPolicy, fieldNumber: 6) - } - try { if let v = self._lrsLoadReportingServer { - try visitor.visitSingularMessageField(value: v, fieldNumber: 7) - } }() - if !self.telemetryLabels.isEmpty { - try visitor.visitMapField(fieldType: SwiftProtobuf._ProtobufMap.self, value: self.telemetryLabels, fieldNumber: 8) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_ServiceConfig_XdsClusterImplLoadBalancingPolicyConfig, rhs: Grpc_ServiceConfig_XdsClusterImplLoadBalancingPolicyConfig) -> Bool { - if lhs.cluster != rhs.cluster {return false} - if lhs.childPolicy != rhs.childPolicy {return false} - if lhs.edsServiceName != rhs.edsServiceName {return false} - if lhs._lrsLoadReportingServerName != rhs._lrsLoadReportingServerName {return false} - if lhs._lrsLoadReportingServer != rhs._lrsLoadReportingServer {return false} - if lhs._maxConcurrentRequests != rhs._maxConcurrentRequests {return false} - if lhs.dropCategories != rhs.dropCategories {return false} - if lhs.telemetryLabels != rhs.telemetryLabels {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_ServiceConfig_XdsClusterImplLoadBalancingPolicyConfig.DropCategory: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = Grpc_ServiceConfig_XdsClusterImplLoadBalancingPolicyConfig.protoMessageName + ".DropCategory" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "category"), - 2: .standard(proto: "requests_per_million"), - ] - - 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.category) }() - case 2: try { try decoder.decodeSingularUInt32Field(value: &self.requestsPerMillion) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if !self.category.isEmpty { - try visitor.visitSingularStringField(value: self.category, fieldNumber: 1) - } - if self.requestsPerMillion != 0 { - try visitor.visitSingularUInt32Field(value: self.requestsPerMillion, fieldNumber: 2) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_ServiceConfig_XdsClusterImplLoadBalancingPolicyConfig.DropCategory, rhs: Grpc_ServiceConfig_XdsClusterImplLoadBalancingPolicyConfig.DropCategory) -> Bool { - if lhs.category != rhs.category {return false} - if lhs.requestsPerMillion != rhs.requestsPerMillion {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_ServiceConfig_RingHashLoadBalancingConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".RingHashLoadBalancingConfig" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "min_ring_size"), - 2: .standard(proto: "max_ring_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.decodeSingularUInt64Field(value: &self.minRingSize) }() - case 2: try { try decoder.decodeSingularUInt64Field(value: &self.maxRingSize) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if self.minRingSize != 0 { - try visitor.visitSingularUInt64Field(value: self.minRingSize, fieldNumber: 1) - } - if self.maxRingSize != 0 { - try visitor.visitSingularUInt64Field(value: self.maxRingSize, fieldNumber: 2) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_ServiceConfig_RingHashLoadBalancingConfig, rhs: Grpc_ServiceConfig_RingHashLoadBalancingConfig) -> Bool { - if lhs.minRingSize != rhs.minRingSize {return false} - if lhs.maxRingSize != rhs.maxRingSize {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_ServiceConfig_XdsWrrLocalityLoadBalancingPolicyConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".XdsWrrLocalityLoadBalancingPolicyConfig" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "child_policy"), - ] - - 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.childPolicy) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if !self.childPolicy.isEmpty { - try visitor.visitRepeatedMessageField(value: self.childPolicy, fieldNumber: 1) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_ServiceConfig_XdsWrrLocalityLoadBalancingPolicyConfig, rhs: Grpc_ServiceConfig_XdsWrrLocalityLoadBalancingPolicyConfig) -> Bool { - if lhs.childPolicy != rhs.childPolicy {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_ServiceConfig_LeastRequestLocalityLoadBalancingPolicyConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".LeastRequestLocalityLoadBalancingPolicyConfig" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "choice_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.decodeSingularUInt64Field(value: &self.choiceCount) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if self.choiceCount != 0 { - try visitor.visitSingularUInt64Field(value: self.choiceCount, fieldNumber: 1) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_ServiceConfig_LeastRequestLocalityLoadBalancingPolicyConfig, rhs: Grpc_ServiceConfig_LeastRequestLocalityLoadBalancingPolicyConfig) -> Bool { - if lhs.choiceCount != rhs.choiceCount {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_ServiceConfig_OverrideHostLoadBalancingPolicyConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".OverrideHostLoadBalancingPolicyConfig" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 3: .standard(proto: "cluster_name"), - 2: .standard(proto: "child_policy"), - 1: .standard(proto: "override_host_status"), - ] - - 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.overrideHostStatus) }() - case 2: try { try decoder.decodeRepeatedMessageField(value: &self.childPolicy) }() - case 3: try { try decoder.decodeSingularStringField(value: &self.clusterName) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if !self.overrideHostStatus.isEmpty { - try visitor.visitPackedEnumField(value: self.overrideHostStatus, fieldNumber: 1) - } - if !self.childPolicy.isEmpty { - try visitor.visitRepeatedMessageField(value: self.childPolicy, fieldNumber: 2) - } - if !self.clusterName.isEmpty { - try visitor.visitSingularStringField(value: self.clusterName, fieldNumber: 3) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_ServiceConfig_OverrideHostLoadBalancingPolicyConfig, rhs: Grpc_ServiceConfig_OverrideHostLoadBalancingPolicyConfig) -> Bool { - if lhs.clusterName != rhs.clusterName {return false} - if lhs.childPolicy != rhs.childPolicy {return false} - if lhs.overrideHostStatus != rhs.overrideHostStatus {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_ServiceConfig_OverrideHostLoadBalancingPolicyConfig.HealthStatus: SwiftProtobuf._ProtoNameProviding { - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 0: .same(proto: "UNKNOWN"), - 1: .same(proto: "HEALTHY"), - 3: .same(proto: "DRAINING"), - ] -} - -extension Grpc_ServiceConfig_LoadBalancingConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".LoadBalancingConfig" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 4: .same(proto: "pick_first"), - 1: .same(proto: "round_robin"), - 20: .same(proto: "weighted_round_robin"), - 3: .same(proto: "grpclb"), - 9: .same(proto: "priority_experimental"), - 10: .same(proto: "weighted_target_experimental"), - 15: .unique(proto: "outlier_detection", json: "outlier_detection_experimental"), - 19: .unique(proto: "rls", json: "rls_experimental"), - 14: .same(proto: "xds_cluster_manager_experimental"), - 6: .same(proto: "cds_experimental"), - 12: .same(proto: "xds_cluster_impl_experimental"), - 18: .same(proto: "override_host_experimental"), - 16: .same(proto: "xds_wrr_locality_experimental"), - 13: .same(proto: "ring_hash_experimental"), - 17: .same(proto: "least_request_experimental"), - 11: .same(proto: "xds_cluster_resolver_experimental"), - 8: .same(proto: "lrs_experimental"), - 7: .same(proto: "eds_experimental"), - 2: .same(proto: "xds"), - 5: .same(proto: "xds_experimental"), - ] - - 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_ServiceConfig_RoundRobinConfig? - var hadOneofValue = false - if let current = self.policy { - hadOneofValue = true - if case .roundRobin(let m) = current {v = m} - } - try decoder.decodeSingularMessageField(value: &v) - if let v = v { - if hadOneofValue {try decoder.handleConflictingOneOf()} - self.policy = .roundRobin(v) - } - }() - case 2: try { - var v: Grpc_ServiceConfig_XdsConfig? - var hadOneofValue = false - if let current = self.policy { - hadOneofValue = true - if case .xds(let m) = current {v = m} - } - try decoder.decodeSingularMessageField(value: &v) - if let v = v { - if hadOneofValue {try decoder.handleConflictingOneOf()} - self.policy = .xds(v) - } - }() - case 3: try { - var v: Grpc_ServiceConfig_GrpcLbConfig? - var hadOneofValue = false - if let current = self.policy { - hadOneofValue = true - if case .grpclb(let m) = current {v = m} - } - try decoder.decodeSingularMessageField(value: &v) - if let v = v { - if hadOneofValue {try decoder.handleConflictingOneOf()} - self.policy = .grpclb(v) - } - }() - case 4: try { - var v: Grpc_ServiceConfig_PickFirstConfig? - var hadOneofValue = false - if let current = self.policy { - hadOneofValue = true - if case .pickFirst(let m) = current {v = m} - } - try decoder.decodeSingularMessageField(value: &v) - if let v = v { - if hadOneofValue {try decoder.handleConflictingOneOf()} - self.policy = .pickFirst(v) - } - }() - case 5: try { - var v: Grpc_ServiceConfig_XdsConfig? - var hadOneofValue = false - if let current = self.policy { - hadOneofValue = true - if case .xdsExperimental(let m) = current {v = m} - } - try decoder.decodeSingularMessageField(value: &v) - if let v = v { - if hadOneofValue {try decoder.handleConflictingOneOf()} - self.policy = .xdsExperimental(v) - } - }() - case 6: try { - var v: Grpc_ServiceConfig_CdsConfig? - var hadOneofValue = false - if let current = self.policy { - hadOneofValue = true - if case .cdsExperimental(let m) = current {v = m} - } - try decoder.decodeSingularMessageField(value: &v) - if let v = v { - if hadOneofValue {try decoder.handleConflictingOneOf()} - self.policy = .cdsExperimental(v) - } - }() - case 7: try { - var v: Grpc_ServiceConfig_EdsLoadBalancingPolicyConfig? - var hadOneofValue = false - if let current = self.policy { - hadOneofValue = true - if case .edsExperimental(let m) = current {v = m} - } - try decoder.decodeSingularMessageField(value: &v) - if let v = v { - if hadOneofValue {try decoder.handleConflictingOneOf()} - self.policy = .edsExperimental(v) - } - }() - case 8: try { - var v: Grpc_ServiceConfig_LrsLoadBalancingPolicyConfig? - var hadOneofValue = false - if let current = self.policy { - hadOneofValue = true - if case .lrsExperimental(let m) = current {v = m} - } - try decoder.decodeSingularMessageField(value: &v) - if let v = v { - if hadOneofValue {try decoder.handleConflictingOneOf()} - self.policy = .lrsExperimental(v) - } - }() - case 9: try { - var v: Grpc_ServiceConfig_PriorityLoadBalancingPolicyConfig? - var hadOneofValue = false - if let current = self.policy { - hadOneofValue = true - if case .priorityExperimental(let m) = current {v = m} - } - try decoder.decodeSingularMessageField(value: &v) - if let v = v { - if hadOneofValue {try decoder.handleConflictingOneOf()} - self.policy = .priorityExperimental(v) - } - }() - case 10: try { - var v: Grpc_ServiceConfig_WeightedTargetLoadBalancingPolicyConfig? - var hadOneofValue = false - if let current = self.policy { - hadOneofValue = true - if case .weightedTargetExperimental(let m) = current {v = m} - } - try decoder.decodeSingularMessageField(value: &v) - if let v = v { - if hadOneofValue {try decoder.handleConflictingOneOf()} - self.policy = .weightedTargetExperimental(v) - } - }() - case 11: try { - var v: Grpc_ServiceConfig_XdsClusterResolverLoadBalancingPolicyConfig? - var hadOneofValue = false - if let current = self.policy { - hadOneofValue = true - if case .xdsClusterResolverExperimental(let m) = current {v = m} - } - try decoder.decodeSingularMessageField(value: &v) - if let v = v { - if hadOneofValue {try decoder.handleConflictingOneOf()} - self.policy = .xdsClusterResolverExperimental(v) - } - }() - case 12: try { - var v: Grpc_ServiceConfig_XdsClusterImplLoadBalancingPolicyConfig? - var hadOneofValue = false - if let current = self.policy { - hadOneofValue = true - if case .xdsClusterImplExperimental(let m) = current {v = m} - } - try decoder.decodeSingularMessageField(value: &v) - if let v = v { - if hadOneofValue {try decoder.handleConflictingOneOf()} - self.policy = .xdsClusterImplExperimental(v) - } - }() - case 13: try { - var v: Grpc_ServiceConfig_RingHashLoadBalancingConfig? - var hadOneofValue = false - if let current = self.policy { - hadOneofValue = true - if case .ringHashExperimental(let m) = current {v = m} - } - try decoder.decodeSingularMessageField(value: &v) - if let v = v { - if hadOneofValue {try decoder.handleConflictingOneOf()} - self.policy = .ringHashExperimental(v) - } - }() - case 14: try { - var v: Grpc_ServiceConfig_XdsClusterManagerLoadBalancingPolicyConfig? - var hadOneofValue = false - if let current = self.policy { - hadOneofValue = true - if case .xdsClusterManagerExperimental(let m) = current {v = m} - } - try decoder.decodeSingularMessageField(value: &v) - if let v = v { - if hadOneofValue {try decoder.handleConflictingOneOf()} - self.policy = .xdsClusterManagerExperimental(v) - } - }() - case 15: try { - var v: Grpc_ServiceConfig_OutlierDetectionLoadBalancingConfig? - var hadOneofValue = false - if let current = self.policy { - hadOneofValue = true - if case .outlierDetection(let m) = current {v = m} - } - try decoder.decodeSingularMessageField(value: &v) - if let v = v { - if hadOneofValue {try decoder.handleConflictingOneOf()} - self.policy = .outlierDetection(v) - } - }() - case 16: try { - var v: Grpc_ServiceConfig_XdsWrrLocalityLoadBalancingPolicyConfig? - var hadOneofValue = false - if let current = self.policy { - hadOneofValue = true - if case .xdsWrrLocalityExperimental(let m) = current {v = m} - } - try decoder.decodeSingularMessageField(value: &v) - if let v = v { - if hadOneofValue {try decoder.handleConflictingOneOf()} - self.policy = .xdsWrrLocalityExperimental(v) - } - }() - case 17: try { - var v: Grpc_ServiceConfig_LeastRequestLocalityLoadBalancingPolicyConfig? - var hadOneofValue = false - if let current = self.policy { - hadOneofValue = true - if case .leastRequestExperimental(let m) = current {v = m} - } - try decoder.decodeSingularMessageField(value: &v) - if let v = v { - if hadOneofValue {try decoder.handleConflictingOneOf()} - self.policy = .leastRequestExperimental(v) - } - }() - case 18: try { - var v: Grpc_ServiceConfig_OverrideHostLoadBalancingPolicyConfig? - var hadOneofValue = false - if let current = self.policy { - hadOneofValue = true - if case .overrideHostExperimental(let m) = current {v = m} - } - try decoder.decodeSingularMessageField(value: &v) - if let v = v { - if hadOneofValue {try decoder.handleConflictingOneOf()} - self.policy = .overrideHostExperimental(v) - } - }() - case 19: try { - var v: Grpc_ServiceConfig_RlsLoadBalancingPolicyConfig? - var hadOneofValue = false - if let current = self.policy { - hadOneofValue = true - if case .rls(let m) = current {v = m} - } - try decoder.decodeSingularMessageField(value: &v) - if let v = v { - if hadOneofValue {try decoder.handleConflictingOneOf()} - self.policy = .rls(v) - } - }() - case 20: try { - var v: Grpc_ServiceConfig_WeightedRoundRobinLbConfig? - var hadOneofValue = false - if let current = self.policy { - hadOneofValue = true - if case .weightedRoundRobin(let m) = current {v = m} - } - try decoder.decodeSingularMessageField(value: &v) - if let v = v { - if hadOneofValue {try decoder.handleConflictingOneOf()} - self.policy = .weightedRoundRobin(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.policy { - case .roundRobin?: try { - guard case .roundRobin(let v)? = self.policy else { preconditionFailure() } - try visitor.visitSingularMessageField(value: v, fieldNumber: 1) - }() - case .xds?: try { - guard case .xds(let v)? = self.policy else { preconditionFailure() } - try visitor.visitSingularMessageField(value: v, fieldNumber: 2) - }() - case .grpclb?: try { - guard case .grpclb(let v)? = self.policy else { preconditionFailure() } - try visitor.visitSingularMessageField(value: v, fieldNumber: 3) - }() - case .pickFirst?: try { - guard case .pickFirst(let v)? = self.policy else { preconditionFailure() } - try visitor.visitSingularMessageField(value: v, fieldNumber: 4) - }() - case .xdsExperimental?: try { - guard case .xdsExperimental(let v)? = self.policy else { preconditionFailure() } - try visitor.visitSingularMessageField(value: v, fieldNumber: 5) - }() - case .cdsExperimental?: try { - guard case .cdsExperimental(let v)? = self.policy else { preconditionFailure() } - try visitor.visitSingularMessageField(value: v, fieldNumber: 6) - }() - case .edsExperimental?: try { - guard case .edsExperimental(let v)? = self.policy else { preconditionFailure() } - try visitor.visitSingularMessageField(value: v, fieldNumber: 7) - }() - case .lrsExperimental?: try { - guard case .lrsExperimental(let v)? = self.policy else { preconditionFailure() } - try visitor.visitSingularMessageField(value: v, fieldNumber: 8) - }() - case .priorityExperimental?: try { - guard case .priorityExperimental(let v)? = self.policy else { preconditionFailure() } - try visitor.visitSingularMessageField(value: v, fieldNumber: 9) - }() - case .weightedTargetExperimental?: try { - guard case .weightedTargetExperimental(let v)? = self.policy else { preconditionFailure() } - try visitor.visitSingularMessageField(value: v, fieldNumber: 10) - }() - case .xdsClusterResolverExperimental?: try { - guard case .xdsClusterResolverExperimental(let v)? = self.policy else { preconditionFailure() } - try visitor.visitSingularMessageField(value: v, fieldNumber: 11) - }() - case .xdsClusterImplExperimental?: try { - guard case .xdsClusterImplExperimental(let v)? = self.policy else { preconditionFailure() } - try visitor.visitSingularMessageField(value: v, fieldNumber: 12) - }() - case .ringHashExperimental?: try { - guard case .ringHashExperimental(let v)? = self.policy else { preconditionFailure() } - try visitor.visitSingularMessageField(value: v, fieldNumber: 13) - }() - case .xdsClusterManagerExperimental?: try { - guard case .xdsClusterManagerExperimental(let v)? = self.policy else { preconditionFailure() } - try visitor.visitSingularMessageField(value: v, fieldNumber: 14) - }() - case .outlierDetection?: try { - guard case .outlierDetection(let v)? = self.policy else { preconditionFailure() } - try visitor.visitSingularMessageField(value: v, fieldNumber: 15) - }() - case .xdsWrrLocalityExperimental?: try { - guard case .xdsWrrLocalityExperimental(let v)? = self.policy else { preconditionFailure() } - try visitor.visitSingularMessageField(value: v, fieldNumber: 16) - }() - case .leastRequestExperimental?: try { - guard case .leastRequestExperimental(let v)? = self.policy else { preconditionFailure() } - try visitor.visitSingularMessageField(value: v, fieldNumber: 17) - }() - case .overrideHostExperimental?: try { - guard case .overrideHostExperimental(let v)? = self.policy else { preconditionFailure() } - try visitor.visitSingularMessageField(value: v, fieldNumber: 18) - }() - case .rls?: try { - guard case .rls(let v)? = self.policy else { preconditionFailure() } - try visitor.visitSingularMessageField(value: v, fieldNumber: 19) - }() - case .weightedRoundRobin?: try { - guard case .weightedRoundRobin(let v)? = self.policy else { preconditionFailure() } - try visitor.visitSingularMessageField(value: v, fieldNumber: 20) - }() - case nil: break - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_ServiceConfig_LoadBalancingConfig, rhs: Grpc_ServiceConfig_LoadBalancingConfig) -> Bool { - if lhs.policy != rhs.policy {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_ServiceConfig_ServiceConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".ServiceConfig" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "load_balancing_policy"), - 4: .standard(proto: "load_balancing_config"), - 2: .standard(proto: "method_config"), - 3: .standard(proto: "retry_throttling"), - 5: .standard(proto: "health_check_config"), - ] - - 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.loadBalancingPolicy) }() - case 2: try { try decoder.decodeRepeatedMessageField(value: &self.methodConfig) }() - case 3: try { try decoder.decodeSingularMessageField(value: &self._retryThrottling) }() - case 4: try { try decoder.decodeRepeatedMessageField(value: &self.loadBalancingConfig) }() - case 5: try { try decoder.decodeSingularMessageField(value: &self._healthCheckConfig) }() - 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.loadBalancingPolicy != .unspecified { - try visitor.visitSingularEnumField(value: self.loadBalancingPolicy, fieldNumber: 1) - } - if !self.methodConfig.isEmpty { - try visitor.visitRepeatedMessageField(value: self.methodConfig, fieldNumber: 2) - } - try { if let v = self._retryThrottling { - try visitor.visitSingularMessageField(value: v, fieldNumber: 3) - } }() - if !self.loadBalancingConfig.isEmpty { - try visitor.visitRepeatedMessageField(value: self.loadBalancingConfig, fieldNumber: 4) - } - try { if let v = self._healthCheckConfig { - try visitor.visitSingularMessageField(value: v, fieldNumber: 5) - } }() - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_ServiceConfig_ServiceConfig, rhs: Grpc_ServiceConfig_ServiceConfig) -> Bool { - if lhs.loadBalancingPolicy != rhs.loadBalancingPolicy {return false} - if lhs.loadBalancingConfig != rhs.loadBalancingConfig {return false} - if lhs.methodConfig != rhs.methodConfig {return false} - if lhs._retryThrottling != rhs._retryThrottling {return false} - if lhs._healthCheckConfig != rhs._healthCheckConfig {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_ServiceConfig_ServiceConfig.LoadBalancingPolicy: SwiftProtobuf._ProtoNameProviding { - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 0: .same(proto: "UNSPECIFIED"), - 1: .same(proto: "ROUND_ROBIN"), - ] -} - -extension Grpc_ServiceConfig_ServiceConfig.RetryThrottlingPolicy: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = Grpc_ServiceConfig_ServiceConfig.protoMessageName + ".RetryThrottlingPolicy" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "max_tokens"), - 2: .standard(proto: "token_ratio"), - ] - - 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.decodeSingularUInt32Field(value: &self.maxTokens) }() - case 2: try { try decoder.decodeSingularFloatField(value: &self.tokenRatio) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if self.maxTokens != 0 { - try visitor.visitSingularUInt32Field(value: self.maxTokens, fieldNumber: 1) - } - if self.tokenRatio.bitPattern != 0 { - try visitor.visitSingularFloatField(value: self.tokenRatio, fieldNumber: 2) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_ServiceConfig_ServiceConfig.RetryThrottlingPolicy, rhs: Grpc_ServiceConfig_ServiceConfig.RetryThrottlingPolicy) -> Bool { - if lhs.maxTokens != rhs.maxTokens {return false} - if lhs.tokenRatio != rhs.tokenRatio {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_ServiceConfig_ServiceConfig.HealthCheckConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = Grpc_ServiceConfig_ServiceConfig.protoMessageName + ".HealthCheckConfig" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "service_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.decodeSingularMessageField(value: &self._serviceName) }() - 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._serviceName { - try visitor.visitSingularMessageField(value: v, fieldNumber: 1) - } }() - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_ServiceConfig_ServiceConfig.HealthCheckConfig, rhs: Grpc_ServiceConfig_ServiceConfig.HealthCheckConfig) -> Bool { - if lhs._serviceName != rhs._serviceName {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_ServiceConfig_XdsServer: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".XdsServer" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "server_uri"), - 2: .same(proto: "channel_creds"), - 3: .same(proto: "server_features"), - ] - - 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.serverUri) }() - case 2: try { try decoder.decodeRepeatedMessageField(value: &self.channelCreds) }() - case 3: try { try decoder.decodeRepeatedMessageField(value: &self.serverFeatures) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if !self.serverUri.isEmpty { - try visitor.visitSingularStringField(value: self.serverUri, fieldNumber: 1) - } - if !self.channelCreds.isEmpty { - try visitor.visitRepeatedMessageField(value: self.channelCreds, fieldNumber: 2) - } - if !self.serverFeatures.isEmpty { - try visitor.visitRepeatedMessageField(value: self.serverFeatures, fieldNumber: 3) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_ServiceConfig_XdsServer, rhs: Grpc_ServiceConfig_XdsServer) -> Bool { - if lhs.serverUri != rhs.serverUri {return false} - if lhs.channelCreds != rhs.channelCreds {return false} - if lhs.serverFeatures != rhs.serverFeatures {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_ServiceConfig_XdsServer.ChannelCredentials: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = Grpc_ServiceConfig_XdsServer.protoMessageName + ".ChannelCredentials" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "type"), - 2: .same(proto: "config"), - ] - - 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.type) }() - case 2: try { try decoder.decodeSingularMessageField(value: &self._config) }() - 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.type.isEmpty { - try visitor.visitSingularStringField(value: self.type, fieldNumber: 1) - } - try { if let v = self._config { - try visitor.visitSingularMessageField(value: v, fieldNumber: 2) - } }() - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_ServiceConfig_XdsServer.ChannelCredentials, rhs: Grpc_ServiceConfig_XdsServer.ChannelCredentials) -> Bool { - if lhs.type != rhs.type {return false} - if lhs._config != rhs._config {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_ServiceConfig_XdsClusterResolverLoadBalancingPolicyConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".XdsClusterResolverLoadBalancingPolicyConfig" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "discovery_mechanisms"), - 2: .standard(proto: "xds_lb_policy"), - ] - - 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.discoveryMechanisms) }() - case 2: try { try decoder.decodeRepeatedMessageField(value: &self.xdsLbPolicy) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if !self.discoveryMechanisms.isEmpty { - try visitor.visitRepeatedMessageField(value: self.discoveryMechanisms, fieldNumber: 1) - } - if !self.xdsLbPolicy.isEmpty { - try visitor.visitRepeatedMessageField(value: self.xdsLbPolicy, fieldNumber: 2) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_ServiceConfig_XdsClusterResolverLoadBalancingPolicyConfig, rhs: Grpc_ServiceConfig_XdsClusterResolverLoadBalancingPolicyConfig) -> Bool { - if lhs.discoveryMechanisms != rhs.discoveryMechanisms {return false} - if lhs.xdsLbPolicy != rhs.xdsLbPolicy {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_ServiceConfig_XdsClusterResolverLoadBalancingPolicyConfig.DiscoveryMechanism: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = Grpc_ServiceConfig_XdsClusterResolverLoadBalancingPolicyConfig.protoMessageName + ".DiscoveryMechanism" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "cluster"), - 2: .standard(proto: "lrs_load_reporting_server_name"), - 7: .standard(proto: "lrs_load_reporting_server"), - 3: .standard(proto: "max_concurrent_requests"), - 4: .same(proto: "type"), - 5: .standard(proto: "eds_service_name"), - 6: .standard(proto: "dns_hostname"), - 8: .standard(proto: "outlier_detection"), - 9: .standard(proto: "override_host_status"), - 10: .standard(proto: "telemetry_labels"), - ] - - fileprivate class _StorageClass { - var _cluster: String = String() - var _lrsLoadReportingServerName: SwiftProtobuf.Google_Protobuf_StringValue? = nil - var _lrsLoadReportingServer: Grpc_ServiceConfig_XdsServer? = nil - var _maxConcurrentRequests: SwiftProtobuf.Google_Protobuf_UInt32Value? = nil - var _type: Grpc_ServiceConfig_XdsClusterResolverLoadBalancingPolicyConfig.DiscoveryMechanism.TypeEnum = .unknown - var _edsServiceName: String = String() - var _dnsHostname: String = String() - var _outlierDetection: Grpc_ServiceConfig_OutlierDetectionLoadBalancingConfig? = nil - var _overrideHostStatus: [Grpc_ServiceConfig_OverrideHostLoadBalancingPolicyConfig.HealthStatus] = [] - var _telemetryLabels: Dictionary = [:] - - #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) { - _cluster = source._cluster - _lrsLoadReportingServerName = source._lrsLoadReportingServerName - _lrsLoadReportingServer = source._lrsLoadReportingServer - _maxConcurrentRequests = source._maxConcurrentRequests - _type = source._type - _edsServiceName = source._edsServiceName - _dnsHostname = source._dnsHostname - _outlierDetection = source._outlierDetection - _overrideHostStatus = source._overrideHostStatus - _telemetryLabels = source._telemetryLabels - } - } - - 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._cluster) }() - case 2: try { try decoder.decodeSingularMessageField(value: &_storage._lrsLoadReportingServerName) }() - case 3: try { try decoder.decodeSingularMessageField(value: &_storage._maxConcurrentRequests) }() - case 4: try { try decoder.decodeSingularEnumField(value: &_storage._type) }() - case 5: try { try decoder.decodeSingularStringField(value: &_storage._edsServiceName) }() - case 6: try { try decoder.decodeSingularStringField(value: &_storage._dnsHostname) }() - case 7: try { try decoder.decodeSingularMessageField(value: &_storage._lrsLoadReportingServer) }() - case 8: try { try decoder.decodeSingularMessageField(value: &_storage._outlierDetection) }() - case 9: try { try decoder.decodeRepeatedEnumField(value: &_storage._overrideHostStatus) }() - case 10: try { try decoder.decodeMapField(fieldType: SwiftProtobuf._ProtobufMap.self, value: &_storage._telemetryLabels) }() - 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._cluster.isEmpty { - try visitor.visitSingularStringField(value: _storage._cluster, fieldNumber: 1) - } - try { if let v = _storage._lrsLoadReportingServerName { - try visitor.visitSingularMessageField(value: v, fieldNumber: 2) - } }() - try { if let v = _storage._maxConcurrentRequests { - try visitor.visitSingularMessageField(value: v, fieldNumber: 3) - } }() - if _storage._type != .unknown { - try visitor.visitSingularEnumField(value: _storage._type, fieldNumber: 4) - } - if !_storage._edsServiceName.isEmpty { - try visitor.visitSingularStringField(value: _storage._edsServiceName, fieldNumber: 5) - } - if !_storage._dnsHostname.isEmpty { - try visitor.visitSingularStringField(value: _storage._dnsHostname, fieldNumber: 6) - } - try { if let v = _storage._lrsLoadReportingServer { - try visitor.visitSingularMessageField(value: v, fieldNumber: 7) - } }() - try { if let v = _storage._outlierDetection { - try visitor.visitSingularMessageField(value: v, fieldNumber: 8) - } }() - if !_storage._overrideHostStatus.isEmpty { - try visitor.visitPackedEnumField(value: _storage._overrideHostStatus, fieldNumber: 9) - } - if !_storage._telemetryLabels.isEmpty { - try visitor.visitMapField(fieldType: SwiftProtobuf._ProtobufMap.self, value: _storage._telemetryLabels, fieldNumber: 10) - } - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_ServiceConfig_XdsClusterResolverLoadBalancingPolicyConfig.DiscoveryMechanism, rhs: Grpc_ServiceConfig_XdsClusterResolverLoadBalancingPolicyConfig.DiscoveryMechanism) -> 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._cluster != rhs_storage._cluster {return false} - if _storage._lrsLoadReportingServerName != rhs_storage._lrsLoadReportingServerName {return false} - if _storage._lrsLoadReportingServer != rhs_storage._lrsLoadReportingServer {return false} - if _storage._maxConcurrentRequests != rhs_storage._maxConcurrentRequests {return false} - if _storage._type != rhs_storage._type {return false} - if _storage._edsServiceName != rhs_storage._edsServiceName {return false} - if _storage._dnsHostname != rhs_storage._dnsHostname {return false} - if _storage._outlierDetection != rhs_storage._outlierDetection {return false} - if _storage._overrideHostStatus != rhs_storage._overrideHostStatus {return false} - if _storage._telemetryLabels != rhs_storage._telemetryLabels {return false} - return true - } - if !storagesAreEqual {return false} - } - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_ServiceConfig_XdsClusterResolverLoadBalancingPolicyConfig.DiscoveryMechanism.TypeEnum: SwiftProtobuf._ProtoNameProviding { - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 0: .same(proto: "UNKNOWN"), - 1: .same(proto: "EDS"), - 2: .same(proto: "LOGICAL_DNS"), - ] -} - -extension Grpc_ServiceConfig_LrsLoadBalancingPolicyConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".LrsLoadBalancingPolicyConfig" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "cluster_name"), - 2: .standard(proto: "eds_service_name"), - 3: .standard(proto: "lrs_load_reporting_server_name"), - 4: .same(proto: "locality"), - 5: .standard(proto: "child_policy"), - ] - - 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.clusterName) }() - case 2: try { try decoder.decodeSingularStringField(value: &self.edsServiceName) }() - case 3: try { try decoder.decodeSingularStringField(value: &self.lrsLoadReportingServerName) }() - case 4: try { try decoder.decodeSingularMessageField(value: &self._locality) }() - case 5: try { try decoder.decodeRepeatedMessageField(value: &self.childPolicy) }() - 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.clusterName.isEmpty { - try visitor.visitSingularStringField(value: self.clusterName, fieldNumber: 1) - } - if !self.edsServiceName.isEmpty { - try visitor.visitSingularStringField(value: self.edsServiceName, fieldNumber: 2) - } - if !self.lrsLoadReportingServerName.isEmpty { - try visitor.visitSingularStringField(value: self.lrsLoadReportingServerName, fieldNumber: 3) - } - try { if let v = self._locality { - try visitor.visitSingularMessageField(value: v, fieldNumber: 4) - } }() - if !self.childPolicy.isEmpty { - try visitor.visitRepeatedMessageField(value: self.childPolicy, fieldNumber: 5) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_ServiceConfig_LrsLoadBalancingPolicyConfig, rhs: Grpc_ServiceConfig_LrsLoadBalancingPolicyConfig) -> Bool { - if lhs.clusterName != rhs.clusterName {return false} - if lhs.edsServiceName != rhs.edsServiceName {return false} - if lhs.lrsLoadReportingServerName != rhs.lrsLoadReportingServerName {return false} - if lhs._locality != rhs._locality {return false} - if lhs.childPolicy != rhs.childPolicy {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_ServiceConfig_LrsLoadBalancingPolicyConfig.Locality: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = Grpc_ServiceConfig_LrsLoadBalancingPolicyConfig.protoMessageName + ".Locality" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "region"), - 2: .same(proto: "zone"), - 3: .same(proto: "subzone"), - ] - - 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.region) }() - case 2: try { try decoder.decodeSingularStringField(value: &self.zone) }() - case 3: try { try decoder.decodeSingularStringField(value: &self.subzone) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if !self.region.isEmpty { - try visitor.visitSingularStringField(value: self.region, fieldNumber: 1) - } - if !self.zone.isEmpty { - try visitor.visitSingularStringField(value: self.zone, fieldNumber: 2) - } - if !self.subzone.isEmpty { - try visitor.visitSingularStringField(value: self.subzone, fieldNumber: 3) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_ServiceConfig_LrsLoadBalancingPolicyConfig.Locality, rhs: Grpc_ServiceConfig_LrsLoadBalancingPolicyConfig.Locality) -> Bool { - if lhs.region != rhs.region {return false} - if lhs.zone != rhs.zone {return false} - if lhs.subzone != rhs.subzone {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_ServiceConfig_EdsLoadBalancingPolicyConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".EdsLoadBalancingPolicyConfig" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "cluster"), - 2: .standard(proto: "eds_service_name"), - 3: .standard(proto: "lrs_load_reporting_server_name"), - 4: .standard(proto: "locality_picking_policy"), - 5: .standard(proto: "endpoint_picking_policy"), - ] - - 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.cluster) }() - case 2: try { try decoder.decodeSingularStringField(value: &self.edsServiceName) }() - case 3: try { try decoder.decodeSingularMessageField(value: &self._lrsLoadReportingServerName) }() - case 4: try { try decoder.decodeRepeatedMessageField(value: &self.localityPickingPolicy) }() - case 5: try { try decoder.decodeRepeatedMessageField(value: &self.endpointPickingPolicy) }() - 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.cluster.isEmpty { - try visitor.visitSingularStringField(value: self.cluster, fieldNumber: 1) - } - if !self.edsServiceName.isEmpty { - try visitor.visitSingularStringField(value: self.edsServiceName, fieldNumber: 2) - } - try { if let v = self._lrsLoadReportingServerName { - try visitor.visitSingularMessageField(value: v, fieldNumber: 3) - } }() - if !self.localityPickingPolicy.isEmpty { - try visitor.visitRepeatedMessageField(value: self.localityPickingPolicy, fieldNumber: 4) - } - if !self.endpointPickingPolicy.isEmpty { - try visitor.visitRepeatedMessageField(value: self.endpointPickingPolicy, fieldNumber: 5) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_ServiceConfig_EdsLoadBalancingPolicyConfig, rhs: Grpc_ServiceConfig_EdsLoadBalancingPolicyConfig) -> Bool { - if lhs.cluster != rhs.cluster {return false} - if lhs.edsServiceName != rhs.edsServiceName {return false} - if lhs._lrsLoadReportingServerName != rhs._lrsLoadReportingServerName {return false} - if lhs.localityPickingPolicy != rhs.localityPickingPolicy {return false} - if lhs.endpointPickingPolicy != rhs.endpointPickingPolicy {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension Grpc_ServiceConfig_XdsConfig: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = _protobuf_package + ".XdsConfig" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "balancer_name"), - 2: .standard(proto: "child_policy"), - 3: .standard(proto: "fallback_policy"), - 4: .standard(proto: "eds_service_name"), - 5: .standard(proto: "lrs_load_reporting_server_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.balancerName) }() - case 2: try { try decoder.decodeRepeatedMessageField(value: &self.childPolicy) }() - case 3: try { try decoder.decodeRepeatedMessageField(value: &self.fallbackPolicy) }() - case 4: try { try decoder.decodeSingularStringField(value: &self.edsServiceName) }() - case 5: try { try decoder.decodeSingularMessageField(value: &self._lrsLoadReportingServerName) }() - 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.balancerName.isEmpty { - try visitor.visitSingularStringField(value: self.balancerName, fieldNumber: 1) - } - if !self.childPolicy.isEmpty { - try visitor.visitRepeatedMessageField(value: self.childPolicy, fieldNumber: 2) - } - if !self.fallbackPolicy.isEmpty { - try visitor.visitRepeatedMessageField(value: self.fallbackPolicy, fieldNumber: 3) - } - if !self.edsServiceName.isEmpty { - try visitor.visitSingularStringField(value: self.edsServiceName, fieldNumber: 4) - } - try { if let v = self._lrsLoadReportingServerName { - try visitor.visitSingularMessageField(value: v, fieldNumber: 5) - } }() - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: Grpc_ServiceConfig_XdsConfig, rhs: Grpc_ServiceConfig_XdsConfig) -> Bool { - if lhs.balancerName != rhs.balancerName {return false} - if lhs.childPolicy != rhs.childPolicy {return false} - if lhs.fallbackPolicy != rhs.fallbackPolicy {return false} - if lhs.edsServiceName != rhs.edsServiceName {return false} - if lhs._lrsLoadReportingServerName != rhs._lrsLoadReportingServerName {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} diff --git a/Tests/GRPCCoreTests/Configuration/Inputs/method_config.hedging_policy.invalid.max_attempts.json b/Tests/GRPCCoreTests/Configuration/Inputs/method_config.hedging_policy.invalid.max_attempts.json deleted file mode 100644 index 9436d324b..000000000 --- a/Tests/GRPCCoreTests/Configuration/Inputs/method_config.hedging_policy.invalid.max_attempts.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "maxAttempts": 1, - "hedgingDelay": "1s", - "nonFatalStatusCodes": ["ABORTED"] -} diff --git a/Tests/GRPCCoreTests/Configuration/Inputs/method_config.hedging_policy.json b/Tests/GRPCCoreTests/Configuration/Inputs/method_config.hedging_policy.json deleted file mode 100644 index 8dd9ed8e9..000000000 --- a/Tests/GRPCCoreTests/Configuration/Inputs/method_config.hedging_policy.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "maxAttempts": 3, - "hedgingDelay": "1s", - "nonFatalStatusCodes": ["ABORTED"] -} diff --git a/Tests/GRPCCoreTests/Configuration/Inputs/method_config.json b/Tests/GRPCCoreTests/Configuration/Inputs/method_config.json deleted file mode 100644 index 71c9c2cab..000000000 --- a/Tests/GRPCCoreTests/Configuration/Inputs/method_config.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": [ - { - "service": "echo.Echo", - "method": "Get" - } - ], - "waitForReady": true, - "timeout": "1s", - "maxRequestMessageBytes": 1024, - "maxResponseMessageBytes": 2048 -} diff --git a/Tests/GRPCCoreTests/Configuration/Inputs/method_config.name.empty.json b/Tests/GRPCCoreTests/Configuration/Inputs/method_config.name.empty.json deleted file mode 100644 index 0967ef424..000000000 --- a/Tests/GRPCCoreTests/Configuration/Inputs/method_config.name.empty.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/Tests/GRPCCoreTests/Configuration/Inputs/method_config.name.full.json b/Tests/GRPCCoreTests/Configuration/Inputs/method_config.name.full.json deleted file mode 100644 index ed21cc360..000000000 --- a/Tests/GRPCCoreTests/Configuration/Inputs/method_config.name.full.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "service": "foo.bar", - "method": "baz" -} diff --git a/Tests/GRPCCoreTests/Configuration/Inputs/method_config.name.service_only.json b/Tests/GRPCCoreTests/Configuration/Inputs/method_config.name.service_only.json deleted file mode 100644 index beb50d5e3..000000000 --- a/Tests/GRPCCoreTests/Configuration/Inputs/method_config.name.service_only.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "service": "foo.bar" -} diff --git a/Tests/GRPCCoreTests/Configuration/Inputs/method_config.retry_policy.invalid.backoff_multiplier.json b/Tests/GRPCCoreTests/Configuration/Inputs/method_config.retry_policy.invalid.backoff_multiplier.json deleted file mode 100644 index a43451a94..000000000 --- a/Tests/GRPCCoreTests/Configuration/Inputs/method_config.retry_policy.invalid.backoff_multiplier.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "maxAttempts": 3, - "initialBackoff": "1s", - "maxBackoff": "3s", - "backoffMultiplier": -1.6, - "retryableStatusCodes": ["ABORTED", "UNAVAILABLE"] -} diff --git a/Tests/GRPCCoreTests/Configuration/Inputs/method_config.retry_policy.invalid.initial_backoff.json b/Tests/GRPCCoreTests/Configuration/Inputs/method_config.retry_policy.invalid.initial_backoff.json deleted file mode 100644 index bb9691bbb..000000000 --- a/Tests/GRPCCoreTests/Configuration/Inputs/method_config.retry_policy.invalid.initial_backoff.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "maxAttempts": 3, - "initialBackoff": "0s", - "maxBackoff": "3s", - "backoffMultiplier": 1.6, - "retryableStatusCodes": ["ABORTED", "UNAVAILABLE"] -} diff --git a/Tests/GRPCCoreTests/Configuration/Inputs/method_config.retry_policy.invalid.max_attempts.json b/Tests/GRPCCoreTests/Configuration/Inputs/method_config.retry_policy.invalid.max_attempts.json deleted file mode 100644 index 454ba94e9..000000000 --- a/Tests/GRPCCoreTests/Configuration/Inputs/method_config.retry_policy.invalid.max_attempts.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "maxAttempts": 1, - "initialBackoff": "1s", - "maxBackoff": "3s", - "backoffMultiplier": 1.6, - "retryableStatusCodes": ["ABORTED", "UNAVAILABLE"] -} diff --git a/Tests/GRPCCoreTests/Configuration/Inputs/method_config.retry_policy.invalid.max_backoff.json b/Tests/GRPCCoreTests/Configuration/Inputs/method_config.retry_policy.invalid.max_backoff.json deleted file mode 100644 index 6059280be..000000000 --- a/Tests/GRPCCoreTests/Configuration/Inputs/method_config.retry_policy.invalid.max_backoff.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "maxAttempts": 3, - "initialBackoff": "1s", - "maxBackoff": "0s", - "backoffMultiplier": 1.6, - "retryableStatusCodes": ["ABORTED", "UNAVAILABLE"] -} diff --git a/Tests/GRPCCoreTests/Configuration/Inputs/method_config.retry_policy.invalid.retryable_status_codes.json b/Tests/GRPCCoreTests/Configuration/Inputs/method_config.retry_policy.invalid.retryable_status_codes.json deleted file mode 100644 index d437878f0..000000000 --- a/Tests/GRPCCoreTests/Configuration/Inputs/method_config.retry_policy.invalid.retryable_status_codes.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "maxAttempts": 3, - "initialBackoff": "1s", - "maxBackoff": "3s", - "backoffMultiplier": 1.6, - "retryableStatusCodes": [] -} diff --git a/Tests/GRPCCoreTests/Configuration/Inputs/method_config.retry_policy.json b/Tests/GRPCCoreTests/Configuration/Inputs/method_config.retry_policy.json deleted file mode 100644 index ef8744c2e..000000000 --- a/Tests/GRPCCoreTests/Configuration/Inputs/method_config.retry_policy.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "maxAttempts": 3, - "initialBackoff": "1s", - "maxBackoff": "3s", - "backoffMultiplier": 1.6, - "retryableStatusCodes": ["ABORTED", "UNAVAILABLE"] -} diff --git a/Tests/GRPCCoreTests/Configuration/Inputs/method_config.with_hedging.json b/Tests/GRPCCoreTests/Configuration/Inputs/method_config.with_hedging.json deleted file mode 100644 index 1d9ecc6b7..000000000 --- a/Tests/GRPCCoreTests/Configuration/Inputs/method_config.with_hedging.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": [ - { - "service": "echo.Echo", - "method": "Get" - } - ], - "waitForReady": true, - "timeout": "1s", - "maxRequestMessageBytes": 1024, - "maxResponseMessageBytes": 2048, - "hedgingPolicy": { - "maxAttempts": 3, - "hedgingDelay": "42s", - "nonFatalStatusCodes": [ - "ABORTED", - "UNIMPLEMENTED" - ] - } -} diff --git a/Tests/GRPCCoreTests/Configuration/Inputs/method_config.with_retries.json b/Tests/GRPCCoreTests/Configuration/Inputs/method_config.with_retries.json deleted file mode 100644 index 41556e185..000000000 --- a/Tests/GRPCCoreTests/Configuration/Inputs/method_config.with_retries.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": [ - { - "service": "echo.Echo", - "method": "Get" - } - ], - "waitForReady": true, - "timeout": "1s", - "maxRequestMessageBytes": 1024, - "maxResponseMessageBytes": 2048, - "retryPolicy": { - "maxAttempts": 3, - "initialBackoff": "1s", - "maxBackoff": "3s", - "backoffMultiplier": 1.6, - "retryableStatusCodes": [ - "ABORTED", - "UNIMPLEMENTED" - ] - } -} diff --git a/Tests/GRPCCoreTests/Configuration/MethodConfigCodingTests.swift b/Tests/GRPCCoreTests/Configuration/MethodConfigCodingTests.swift deleted file mode 100644 index d9797343a..000000000 --- a/Tests/GRPCCoreTests/Configuration/MethodConfigCodingTests.swift +++ /dev/null @@ -1,412 +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 SwiftProtobuf -import Testing - -@testable import GRPCCore - -@Suite("MethodConfig coding tests") -struct MethodConfigCodingTests { - @Suite("Encoding") - struct Encoding { - private func encodeToJSON(_ value: some Encodable) throws -> String { - let encoder = JSONEncoder() - encoder.outputFormatting = .sortedKeys - let encoded = try encoder.encode(value) - let json = String(decoding: encoded, as: UTF8.self) - return json - } - - @Test( - "Name", - arguments: [ - ( - MethodConfig.Name(service: "foo.bar", method: "baz"), - #"{"method":"baz","service":"foo.bar"}"# - ), - (MethodConfig.Name(service: "foo.bar", method: ""), #"{"method":"","service":"foo.bar"}"#), - (MethodConfig.Name(service: "", method: ""), #"{"method":"","service":""}"#), - ] as [(MethodConfig.Name, String)] - ) - func methodConfigName(name: MethodConfig.Name, expected: String) throws { - let json = try self.encodeToJSON(name) - #expect(json == expected) - } - - @Test( - "GoogleProtobufDuration", - arguments: [ - (.seconds(1), #""1.0s""#), - (.zero, #""0.0s""#), - (.milliseconds(100_123), #""100.123s""#), - ] as [(Duration, String)] - ) - func protobufDuration(duration: Duration, expected: String) throws { - let json = try self.encodeToJSON(GoogleProtobufDuration(duration: duration)) - #expect(json == expected) - } - - @Test( - "GoogleRPCCode", - arguments: [ - (.ok, #""OK""#), - (.cancelled, #""CANCELLED""#), - (.unknown, #""UNKNOWN""#), - (.invalidArgument, #""INVALID_ARGUMENT""#), - (.deadlineExceeded, #""DEADLINE_EXCEEDED""#), - (.notFound, #""NOT_FOUND""#), - (.alreadyExists, #""ALREADY_EXISTS""#), - (.permissionDenied, #""PERMISSION_DENIED""#), - (.resourceExhausted, #""RESOURCE_EXHAUSTED""#), - (.failedPrecondition, #""FAILED_PRECONDITION""#), - (.aborted, #""ABORTED""#), - (.outOfRange, #""OUT_OF_RANGE""#), - (.unimplemented, #""UNIMPLEMENTED""#), - (.internalError, #""INTERNAL""#), - (.unavailable, #""UNAVAILABLE""#), - (.dataLoss, #""DATA_LOSS""#), - (.unauthenticated, #""UNAUTHENTICATED""#), - ] as [(Status.Code, String)] - ) - func rpcCode(code: Status.Code, expected: String) throws { - let json = try self.encodeToJSON(GoogleRPCCode(code: code)) - #expect(json == expected) - } - - @Test("RetryPolicy") - func retryPolicy() throws { - let policy = RetryPolicy( - maxAttempts: 3, - initialBackoff: .seconds(1), - maxBackoff: .seconds(3), - backoffMultiplier: 1.6, - retryableStatusCodes: [.aborted] - ) - - let json = try self.encodeToJSON(policy) - let expected = - #"{"backoffMultiplier":1.6,"initialBackoff":"1.0s","maxAttempts":3,"maxBackoff":"3.0s","retryableStatusCodes":["ABORTED"]}"# - #expect(json == expected) - } - - @Test("HedgingPolicy") - func hedgingPolicy() throws { - let policy = HedgingPolicy( - maxAttempts: 3, - hedgingDelay: .seconds(1), - nonFatalStatusCodes: [.aborted] - ) - - let json = try self.encodeToJSON(policy) - let expected = #"{"hedgingDelay":"1.0s","maxAttempts":3,"nonFatalStatusCodes":["ABORTED"]}"# - #expect(json == expected) - } - } - - @Suite("Decoding") - struct Decoding { - private func decodeFromFile( - _ name: String, - as: Decoded.Type - ) throws -> Decoded { - let input = Bundle.module.url( - forResource: name, - withExtension: "json", - subdirectory: "Inputs" - ) - - let url = try #require(input) - let data = try Data(contentsOf: url) - - let decoder = JSONDecoder() - return try decoder.decode(Decoded.self, from: data) - } - - private func decodeFromJSONString( - _ json: String, - as: Decoded.Type - ) throws -> Decoded { - let data = Data(json.utf8) - let decoder = JSONDecoder() - return try decoder.decode(Decoded.self, from: data) - } - - private static let codeNames: [String] = [ - "OK", - "CANCELLED", - "UNKNOWN", - "INVALID_ARGUMENT", - "DEADLINE_EXCEEDED", - "NOT_FOUND", - "ALREADY_EXISTS", - "PERMISSION_DENIED", - "RESOURCE_EXHAUSTED", - "FAILED_PRECONDITION", - "ABORTED", - "OUT_OF_RANGE", - "UNIMPLEMENTED", - "INTERNAL", - "UNAVAILABLE", - "DATA_LOSS", - "UNAUTHENTICATED", - ] - - @Test( - "Name", - arguments: [ - ("method_config.name.full", MethodConfig.Name(service: "foo.bar", method: "baz")), - ("method_config.name.service_only", MethodConfig.Name(service: "foo.bar", method: "")), - ("method_config.name.empty", MethodConfig.Name(service: "", method: "")), - ] as [(String, MethodConfig.Name)] - ) - func name(_ fileName: String, expected: MethodConfig.Name) throws { - let decoded = try self.decodeFromFile(fileName, as: MethodConfig.Name.self) - #expect(decoded == expected) - } - - @Test( - "GoogleProtobufDuration", - arguments: [ - ("1.0s", .seconds(1)), - ("1s", .seconds(1)), - ("1.000000s", .seconds(1)), - ("0s", .zero), - ("100.123s", .milliseconds(100_123)), - ] as [(String, Duration)] - ) - func googleProtobufDuration(duration: String, expectedDuration: Duration) throws { - let json = "\"\(duration)\"" - let decoded = try self.decodeFromJSONString(json, as: GoogleProtobufDuration.self) - - // Conversion is lossy as we go from floating point seconds to integer seconds and - // attoseconds. Allow for millisecond precision. - let divisor: Int64 = 1_000_000_000_000_000 - - let duration = decoded.duration.components - let expected = expectedDuration.components - - #expect(duration.seconds == expected.seconds) - #expect(duration.attoseconds / divisor == expected.attoseconds / divisor) - } - - @Test("Invalid GoogleProtobufDuration", arguments: ["1", "1ss", "1S", "1.0S"]) - func googleProtobufDuration(invalidDuration: String) throws { - let json = "\"\(invalidDuration)\"" - #expect { - try self.decodeFromJSONString(json, as: GoogleProtobufDuration.self) - } throws: { error in - guard let error = error as? RuntimeError else { return false } - return error.code == .invalidArgument - } - } - - @Test("GoogleRPCCode from case name", arguments: zip(Self.codeNames, Status.Code.all)) - func rpcCode(name: String, expected: Status.Code) throws { - let json = "\"\(name)\"" - let decoded = try self.decodeFromJSONString(json, as: GoogleRPCCode.self) - #expect(decoded.code == expected) - } - - @Test("GoogleRPCCode from rawValue", arguments: zip(0 ... 16, Status.Code.all)) - func rpcCode(rawValue: Int, expected: Status.Code) throws { - let json = "\(rawValue)" - let decoded = try self.decodeFromJSONString(json, as: GoogleRPCCode.self) - #expect(decoded.code == expected) - } - - @Test("RetryPolicy") - func retryPolicy() throws { - let decoded = try self.decodeFromFile("method_config.retry_policy", as: RetryPolicy.self) - let expected = RetryPolicy( - maxAttempts: 3, - initialBackoff: .seconds(1), - maxBackoff: .seconds(3), - backoffMultiplier: 1.6, - retryableStatusCodes: [.aborted, .unavailable] - ) - #expect(decoded == expected) - } - - @Test( - "RetryPolicy with invalid values", - arguments: [ - "method_config.retry_policy.invalid.backoff_multiplier", - "method_config.retry_policy.invalid.initial_backoff", - "method_config.retry_policy.invalid.max_backoff", - "method_config.retry_policy.invalid.max_attempts", - "method_config.retry_policy.invalid.retryable_status_codes", - ] - ) - func invalidRetryPolicy(fileName: String) throws { - #expect(throws: RuntimeError.self) { - try self.decodeFromFile(fileName, as: RetryPolicy.self) - } - } - - @Test("HedgingPolicy") - func hedgingPolicy() throws { - let decoded = try self.decodeFromFile("method_config.hedging_policy", as: HedgingPolicy.self) - let expected = HedgingPolicy( - maxAttempts: 3, - hedgingDelay: .seconds(1), - nonFatalStatusCodes: [.aborted] - ) - #expect(decoded == expected) - } - - @Test( - "HedgingPolicy with invalid values", - arguments: [ - "method_config.hedging_policy.invalid.max_attempts" - ] - ) - func invalidHedgingPolicy(fileName: String) throws { - #expect(throws: RuntimeError.self) { - try self.decodeFromFile(fileName, as: HedgingPolicy.self) - } - } - - @Test("MethodConfig") - func methodConfig() throws { - let expected = MethodConfig( - names: [ - MethodConfig.Name( - service: "echo.Echo", - method: "Get" - ) - ], - waitForReady: true, - timeout: .seconds(1), - maxRequestMessageBytes: 1024, - maxResponseMessageBytes: 2048 - ) - - let decoded = try self.decodeFromFile("method_config", as: MethodConfig.self) - #expect(decoded == expected) - } - - @Test("MethodConfig with hedging") - func methodConfigWithHedging() throws { - let expected = MethodConfig( - names: [ - MethodConfig.Name( - service: "echo.Echo", - method: "Get" - ) - ], - waitForReady: true, - timeout: .seconds(1), - maxRequestMessageBytes: 1024, - maxResponseMessageBytes: 2048, - executionPolicy: .hedge( - HedgingPolicy( - maxAttempts: 3, - hedgingDelay: .seconds(42), - nonFatalStatusCodes: [.aborted, .unimplemented] - ) - ) - ) - - let decoded = try self.decodeFromFile("method_config.with_hedging", as: MethodConfig.self) - #expect(decoded == expected) - } - - @Test("MethodConfig with retries") - func methodConfigWithRetries() throws { - let expected = MethodConfig( - names: [ - MethodConfig.Name( - service: "echo.Echo", - method: "Get" - ) - ], - waitForReady: true, - timeout: .seconds(1), - maxRequestMessageBytes: 1024, - maxResponseMessageBytes: 2048, - executionPolicy: .retry( - RetryPolicy( - maxAttempts: 3, - initialBackoff: .seconds(1), - maxBackoff: .seconds(3), - backoffMultiplier: 1.6, - retryableStatusCodes: [.aborted, .unimplemented] - ) - ) - ) - - let decoded = try self.decodeFromFile("method_config.with_retries", as: MethodConfig.self) - #expect(decoded == expected) - } - } - - @Suite("Round-trip tests") - struct RoundTrip { - private func decodeFromFile( - _ name: String, - as: Decoded.Type - ) throws -> Decoded { - let input = Bundle.module.url( - forResource: name, - withExtension: "json", - subdirectory: "Inputs" - ) - - let url = try #require(input) - let data = try Data(contentsOf: url) - - let decoder = JSONDecoder() - return try decoder.decode(Decoded.self, from: data) - } - - private func decodeFromJSONString( - _ json: String, - as: Decoded.Type - ) throws -> Decoded { - let data = Data(json.utf8) - let decoder = JSONDecoder() - return try decoder.decode(Decoded.self, from: data) - } - - private func encodeToJSON(_ value: some Encodable) throws -> String { - let encoder = JSONEncoder() - let encoded = try encoder.encode(value) - let json = String(decoding: encoded, as: UTF8.self) - return json - } - - private func roundTrip(type: T.Type = T.self, fileName: String) throws { - let decoded = try self.decodeFromFile(fileName, as: T.self) - let encoded = try self.encodeToJSON(decoded) - let decodedAgain = try self.decodeFromJSONString(encoded, as: T.self) - #expect(decoded == decodedAgain) - } - - @Test( - "MethodConfig", - arguments: [ - "method_config", - "method_config.with_retries", - "method_config.with_hedging", - ] - ) - func roundTripCodingAndDecoding(fileName: String) throws { - try self.roundTrip(type: MethodConfig.self, fileName: fileName) - } - } -} diff --git a/Tests/GRPCCoreTests/Configuration/MethodConfigTests.swift b/Tests/GRPCCoreTests/Configuration/MethodConfigTests.swift deleted file mode 100644 index 8d01bcfd6..000000000 --- a/Tests/GRPCCoreTests/Configuration/MethodConfigTests.swift +++ /dev/null @@ -1,52 +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. - */ - -import GRPCCore -import Testing - -struct MethodConfigTests { - @Test("RetryPolicy clamps max attempts") - func retryPolicyClampsMaxAttempts() { - var policy = RetryPolicy( - maxAttempts: 10, - initialBackoff: .seconds(1), - maxBackoff: .seconds(1), - backoffMultiplier: 1.0, - retryableStatusCodes: [.unavailable] - ) - - // Should be clamped on init - #expect(policy.maxAttempts == 5) - // and when modifying - policy.maxAttempts = 10 - #expect(policy.maxAttempts == 5) - } - - @Test("HedgingPolicy clamps max attempts") - func hedgingPolicyClampsMaxAttempts() { - var policy = HedgingPolicy( - maxAttempts: 10, - hedgingDelay: .seconds(1), - nonFatalStatusCodes: [] - ) - - // Should be clamped on init - #expect(policy.maxAttempts == 5) - // and when modifying - policy.maxAttempts = 10 - #expect(policy.maxAttempts == 5) - } -} diff --git a/Tests/GRPCCoreTests/Configuration/ServiceConfigCodingTests.swift b/Tests/GRPCCoreTests/Configuration/ServiceConfigCodingTests.swift deleted file mode 100644 index 8dbcd7340..000000000 --- a/Tests/GRPCCoreTests/Configuration/ServiceConfigCodingTests.swift +++ /dev/null @@ -1,255 +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 XCTest - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -final class ServiceConfigCodingTests: XCTestCase { - private let encoder = JSONEncoder() - private let decoder = JSONDecoder() - - private func testDecodeThrowsRuntimeError(json: String, as: D.Type) throws { - XCTAssertThrowsError( - ofType: RuntimeError.self, - try self.decoder.decode(D.self, from: Data(json.utf8)) - ) { error in - XCTAssertEqual(error.code, .invalidArgument) - } - } - - private func testRoundTripEncodeDecode(_ value: C) throws { - let encoded = try self.encoder.encode(value) - let decoded = try self.decoder.decode(C.self, from: encoded) - XCTAssertEqual(decoded, value) - } - - func testDecodeRetryThrottlingPolicy() throws { - let json = """ - { - "maxTokens": 10, - "tokenRatio": 0.5 - } - """ - - let expected = try ServiceConfig.RetryThrottling(maxTokens: 10, tokenRatio: 0.5) - let policy = try self.decoder.decode( - ServiceConfig.RetryThrottling.self, - from: Data(json.utf8) - ) - - XCTAssertEqual(policy, expected) - } - - func testEncodeDecodeRetryThrottlingPolicy() throws { - let policy = try ServiceConfig.RetryThrottling(maxTokens: 10, tokenRatio: 0.5) - try self.testRoundTripEncodeDecode(policy) - } - - func testDecodeRetryThrottlingPolicyWithInvalidTokens() throws { - let inputs = ["0", "-1", "-42"] - for input in inputs { - let json = """ - { - "maxTokens": \(input), - "tokenRatio": 0.5 - } - """ - - try self.testDecodeThrowsRuntimeError( - json: json, - as: ServiceConfig.RetryThrottling.self - ) - } - } - - func testDecodeRetryThrottlingPolicyWithInvalidTokenRatio() throws { - let inputs = ["0.0", "-1.0", "-42"] - for input in inputs { - let json = """ - { - "maxTokens": 10, - "tokenRatio": \(input) - } - """ - - try self.testDecodeThrowsRuntimeError( - json: json, - as: ServiceConfig.RetryThrottling.self - ) - } - } - - func testDecodePickFirstPolicy() throws { - let inputs: [(String, ServiceConfig.LoadBalancingConfig.PickFirst)] = [ - (#"{"shuffleAddressList": true}"#, .init(shuffleAddressList: true)), - (#"{"shuffleAddressList": false}"#, .init(shuffleAddressList: false)), - (#"{}"#, .init(shuffleAddressList: false)), - ] - - for (input, expected) in inputs { - let pickFirst = try self.decoder.decode( - ServiceConfig.LoadBalancingConfig.PickFirst.self, - from: Data(input.utf8) - ) - - XCTAssertEqual(pickFirst, expected) - } - } - - func testEncodePickFirstPolicy() throws { - let inputs: [(ServiceConfig.LoadBalancingConfig.PickFirst, String)] = [ - (.init(shuffleAddressList: true), #"{"shuffleAddressList":true}"#), - (.init(shuffleAddressList: false), #"{"shuffleAddressList":false}"#), - ] - - for (input, expected) in inputs { - let encoded = try self.encoder.encode(input) - XCTAssertEqual(String(decoding: encoded, as: UTF8.self), expected) - } - } - - func testDecodeRoundRobinPolicy() throws { - let json = "{}" - let policy = try self.decoder.decode( - ServiceConfig.LoadBalancingConfig.RoundRobin.self, - from: Data(json.utf8) - ) - XCTAssertEqual(policy, ServiceConfig.LoadBalancingConfig.RoundRobin()) - } - - func testEncodeRoundRobinPolicy() throws { - let policy = ServiceConfig.LoadBalancingConfig.RoundRobin() - let encoded = try self.encoder.encode(policy) - XCTAssertEqual(String(decoding: encoded, as: UTF8.self), "{}") - } - - func testDecodeLoadBalancingConfiguration() throws { - let inputs: [(String, ServiceConfig.LoadBalancingConfig)] = [ - (#"{"round_robin": {}}"#, .roundRobin), - (#"{"pick_first": {}}"#, .pickFirst(shuffleAddressList: false)), - (#"{"pick_first": {"shuffleAddressList": false}}"#, .pickFirst(shuffleAddressList: false)), - ] - - for (input, expected) in inputs { - let decoded = try self.decoder.decode( - ServiceConfig.LoadBalancingConfig.self, - from: Data(input.utf8) - ) - XCTAssertEqual(decoded, expected) - } - } - - func testEncodeLoadBalancingConfiguration() throws { - let inputs: [(ServiceConfig.LoadBalancingConfig, String)] = [ - (.roundRobin, #"{"round_robin":{}}"#), - (.pickFirst(shuffleAddressList: false), #"{"pick_first":{"shuffleAddressList":false}}"#), - ] - - for (input, expected) in inputs { - let encoded = try self.encoder.encode(input) - XCTAssertEqual(String(decoding: encoded, as: UTF8.self), expected) - } - } - - func testDecodeServiceConfigFromProtoJSON() throws { - let serviceConfig = Grpc_ServiceConfig_ServiceConfig.with { - $0.methodConfig = [ - Grpc_ServiceConfig_MethodConfig.with { - $0.name = [ - Grpc_ServiceConfig_MethodConfig.Name.with { - $0.service = "foo.Foo" - $0.method = "Bar" - } - ] - $0.timeout = .with { $0.seconds = 1 } - $0.maxRequestMessageBytes = 123 - $0.maxResponseMessageBytes = 456 - } - - ] - $0.loadBalancingConfig = [ - .with { $0.roundRobin = .init() }, - .with { $0.pickFirst = .with { $0.shuffleAddressList = true } }, - ] - $0.retryThrottling = .with { - $0.maxTokens = 10 - $0.tokenRatio = 0.1 - } - } - - let encoded = try serviceConfig.jsonUTF8Data() - let decoded = try self.decoder.decode(ServiceConfig.self, from: encoded) - - let expected = ServiceConfig( - methodConfig: [ - MethodConfig( - names: [ - MethodConfig.Name(service: "foo.Foo", method: "Bar") - ], - timeout: .seconds(1), - maxRequestMessageBytes: 123, - maxResponseMessageBytes: 456 - ) - ], - loadBalancingConfig: [ - .roundRobin, - .pickFirst(shuffleAddressList: true), - ], - retryThrottling: try ServiceConfig.RetryThrottling(maxTokens: 10, tokenRatio: 0.1) - ) - - XCTAssertEqual(decoded, expected) - } - - func testEncodeAndDecodeServiceConfig() throws { - let serviceConfig = ServiceConfig( - methodConfig: [ - MethodConfig( - names: [ - MethodConfig.Name(service: "echo.Echo", method: "Get"), - MethodConfig.Name(service: "greeter.HelloWorld"), - ], - timeout: .seconds(42), - maxRequestMessageBytes: 2048, - maxResponseMessageBytes: 4096, - executionPolicy: .hedge( - HedgingPolicy( - maxAttempts: 3, - hedgingDelay: .seconds(1), - nonFatalStatusCodes: [.aborted] - ) - ) - ), - MethodConfig( - names: [ - MethodConfig.Name(service: "echo.Echo", method: "Update") - ], - timeout: .seconds(300), - maxRequestMessageBytes: 10_000 - ), - ], - loadBalancingConfig: [ - .pickFirst(shuffleAddressList: true), - .roundRobin, - ], - retryThrottling: try ServiceConfig.RetryThrottling(maxTokens: 10, tokenRatio: 3.141) - ) - - try self.testRoundTripEncodeDecode(serviceConfig) - } -} diff --git a/Tests/GRPCCoreTests/GRPCClientTests.swift b/Tests/GRPCCoreTests/GRPCClientTests.swift deleted file mode 100644 index af566279d..000000000 --- a/Tests/GRPCCoreTests/GRPCClientTests.swift +++ /dev/null @@ -1,412 +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. - */ - -import GRPCCore -import GRPCInProcessTransport -import XCTest - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -final class GRPCClientTests: XCTestCase { - func withInProcessConnectedClient( - services: [any RegistrableRPCService], - interceptors: [any ClientInterceptor] = [], - _ body: (GRPCClient, GRPCServer) async throws -> Void - ) async throws { - let inProcess = InProcessTransport.makePair() - let client = GRPCClient(transport: inProcess.client, interceptors: interceptors) - let server = GRPCServer(transport: inProcess.server, services: services) - - try await withThrowingTaskGroup(of: Void.self) { group in - group.addTask { - try await server.serve() - } - - group.addTask { - try await client.run() - } - - // Make sure both server and client are running - try await Task.sleep(for: .milliseconds(100)) - try await body(client, server) - client.beginGracefulShutdown() - server.beginGracefulShutdown() - } - } - - struct IdentitySerializer: MessageSerializer { - typealias Message = [UInt8] - - func serialize(_ message: [UInt8]) throws -> [UInt8] { - return message - } - } - - struct IdentityDeserializer: MessageDeserializer { - typealias Message = [UInt8] - - func deserialize(_ serializedMessageBytes: [UInt8]) throws -> [UInt8] { - return serializedMessageBytes - } - } - - func testUnary() async throws { - try await self.withInProcessConnectedClient(services: [BinaryEcho()]) { client, _ in - try await client.unary( - request: .init(message: [3, 1, 4, 1, 5]), - descriptor: BinaryEcho.Methods.collect, - serializer: IdentitySerializer(), - deserializer: IdentityDeserializer(), - options: .defaults - ) { response in - let message = try response.message - XCTAssertEqual(message, [3, 1, 4, 1, 5]) - } - } - } - - func testClientStreaming() async throws { - try await self.withInProcessConnectedClient(services: [BinaryEcho()]) { client, _ in - try await client.clientStreaming( - request: .init(producer: { writer in - for byte in [3, 1, 4, 1, 5] as [UInt8] { - try await writer.write([byte]) - } - }), - descriptor: BinaryEcho.Methods.collect, - serializer: IdentitySerializer(), - deserializer: IdentityDeserializer(), - options: .defaults - ) { response in - let message = try response.message - XCTAssertEqual(message, [3, 1, 4, 1, 5]) - } - } - } - - func testServerStreaming() async throws { - try await self.withInProcessConnectedClient(services: [BinaryEcho()]) { client, _ in - try await client.serverStreaming( - request: .init(message: [3, 1, 4, 1, 5]), - descriptor: BinaryEcho.Methods.expand, - serializer: IdentitySerializer(), - deserializer: IdentityDeserializer(), - options: .defaults - ) { response in - var responseParts = response.messages.makeAsyncIterator() - for byte in [3, 1, 4, 1, 5] as [UInt8] { - let message = try await responseParts.next() - XCTAssertEqual(message, [byte]) - } - } - } - } - - func testBidirectionalStreaming() async throws { - try await self.withInProcessConnectedClient(services: [BinaryEcho()]) { client, _ in - try await client.bidirectionalStreaming( - request: .init(producer: { writer in - for byte in [3, 1, 4, 1, 5] as [UInt8] { - try await writer.write([byte]) - } - }), - descriptor: BinaryEcho.Methods.update, - serializer: IdentitySerializer(), - deserializer: IdentityDeserializer(), - options: .defaults - ) { response in - var responseParts = response.messages.makeAsyncIterator() - for byte in [3, 1, 4, 1, 5] as [UInt8] { - let message = try await responseParts.next() - XCTAssertEqual(message, [byte]) - } - } - } - } - - func testUnimplementedMethod_Unary() async throws { - try await self.withInProcessConnectedClient(services: [BinaryEcho()]) { client, _ in - try await client.unary( - request: .init(message: [3, 1, 4, 1, 5]), - descriptor: MethodDescriptor(service: "not", method: "implemented"), - serializer: IdentitySerializer(), - deserializer: IdentityDeserializer(), - options: .defaults - ) { response in - XCTAssertThrowsRPCError(try response.accepted.get()) { error in - XCTAssertEqual(error.code, .unimplemented) - } - } - } - } - - func testUnimplementedMethod_ClientStreaming() async throws { - try await self.withInProcessConnectedClient(services: [BinaryEcho()]) { client, _ in - try await client.clientStreaming( - request: .init(producer: { writer in - for byte in [3, 1, 4, 1, 5] as [UInt8] { - try await writer.write([byte]) - } - }), - descriptor: MethodDescriptor(service: "not", method: "implemented"), - serializer: IdentitySerializer(), - deserializer: IdentityDeserializer(), - options: .defaults - ) { response in - XCTAssertThrowsRPCError(try response.accepted.get()) { error in - XCTAssertEqual(error.code, .unimplemented) - } - } - } - } - - func testUnimplementedMethod_ServerStreaming() async throws { - try await self.withInProcessConnectedClient(services: [BinaryEcho()]) { client, _ in - try await client.serverStreaming( - request: .init(message: [3, 1, 4, 1, 5]), - descriptor: MethodDescriptor(service: "not", method: "implemented"), - serializer: IdentitySerializer(), - deserializer: IdentityDeserializer(), - options: .defaults - ) { response in - XCTAssertThrowsRPCError(try response.accepted.get()) { error in - XCTAssertEqual(error.code, .unimplemented) - } - } - } - } - - func testUnimplementedMethod_BidirectionalStreaming() async throws { - try await self.withInProcessConnectedClient(services: [BinaryEcho()]) { client, _ in - try await client.bidirectionalStreaming( - request: .init(producer: { writer in - for byte in [3, 1, 4, 1, 5] as [UInt8] { - try await writer.write([byte]) - } - }), - descriptor: MethodDescriptor(service: "not", method: "implemented"), - serializer: IdentitySerializer(), - deserializer: IdentityDeserializer(), - options: .defaults - ) { response in - XCTAssertThrowsRPCError(try response.accepted.get()) { error in - XCTAssertEqual(error.code, .unimplemented) - } - } - } - } - - func testMultipleConcurrentRequests() async throws { - try await self.withInProcessConnectedClient(services: [BinaryEcho()]) { client, _ in - await withThrowingTaskGroup(of: Void.self) { group in - for i in UInt8.min ..< UInt8.max { - group.addTask { - try await client.unary( - request: .init(message: [i]), - descriptor: BinaryEcho.Methods.collect, - serializer: IdentitySerializer(), - deserializer: IdentityDeserializer(), - options: .defaults - ) { response in - let message = try response.message - XCTAssertEqual(message, [i]) - } - } - } - } - } - } - - func testInterceptorsAreAppliedInOrder() async throws { - let counter1 = AtomicCounter() - let counter2 = AtomicCounter() - - try await self.withInProcessConnectedClient( - services: [BinaryEcho()], - interceptors: [ - .requestCounter(counter1), - .rejectAll(with: RPCError(code: .unavailable, message: "")), - .requestCounter(counter2), - ] - ) { client, _ in - try await client.unary( - request: .init(message: [3, 1, 4, 1, 5]), - descriptor: BinaryEcho.Methods.collect, - serializer: IdentitySerializer(), - deserializer: IdentityDeserializer(), - options: .defaults - ) { response in - XCTAssertRejected(response) { error in - XCTAssertEqual(error.code, .unavailable) - } - } - } - - XCTAssertEqual(counter1.value, 1) - XCTAssertEqual(counter2.value, 0) - } - - func testNoNewRPCsAfterClientClose() async throws { - try await withInProcessConnectedClient(services: [BinaryEcho()]) { client, _ in - // Run an RPC so we know the client is running properly. - try await client.unary( - request: .init(message: [3, 1, 4, 1, 5]), - descriptor: BinaryEcho.Methods.collect, - serializer: IdentitySerializer(), - deserializer: IdentityDeserializer(), - options: .defaults - ) { response in - let message = try response.message - XCTAssertEqual(message, [3, 1, 4, 1, 5]) - } - - // New RPCs should fail immediately after this. - client.beginGracefulShutdown() - - // RPC should fail now. - await XCTAssertThrowsErrorAsync(ofType: RuntimeError.self) { - try await client.unary( - request: .init(message: [3, 1, 4, 1, 5]), - descriptor: BinaryEcho.Methods.collect, - serializer: IdentitySerializer(), - deserializer: IdentityDeserializer(), - options: .defaults - ) { _ in } - } errorHandler: { error in - XCTAssertEqual(error.code, .clientIsStopped) - } - } - } - - func testInFlightRPCsCanContinueAfterClientIsClosed() async throws { - try await withInProcessConnectedClient(services: [BinaryEcho()]) { client, server in - try await client.clientStreaming( - request: .init(producer: { writer in - - // Close the client once this RCP has been started. - client.beginGracefulShutdown() - - // Attempts to start a new RPC should fail. - await XCTAssertThrowsErrorAsync(ofType: RuntimeError.self) { - try await client.unary( - request: .init(message: [3, 1, 4, 1, 5]), - descriptor: BinaryEcho.Methods.collect, - serializer: IdentitySerializer(), - deserializer: IdentityDeserializer(), - options: .defaults - ) { _ in } - } errorHandler: { error in - XCTAssertEqual(error.code, .clientIsStopped) - } - - // Now write to the already opened stream to confirm that opened streams - // can successfully run to completion. - for byte in [3, 1, 4, 1, 5] as [UInt8] { - try await writer.write([byte]) - } - }), - descriptor: BinaryEcho.Methods.collect, - serializer: IdentitySerializer(), - deserializer: IdentityDeserializer(), - options: .defaults - ) { response in - let message = try response.message - XCTAssertEqual(message, [3, 1, 4, 1, 5]) - } - } - } - - func testCancelRunningClient() async throws { - let inProcess = InProcessTransport.makePair() - let client = GRPCClient(transport: inProcess.client) - - try await withThrowingTaskGroup(of: Void.self) { group in - group.addTask { - let server = GRPCServer(transport: inProcess.server, services: [BinaryEcho()]) - try await server.serve() - } - - group.addTask { - try await client.run() - } - - // Wait for client and server to be running. - try await client.unary( - request: .init(message: [3, 1, 4, 1, 5]), - descriptor: BinaryEcho.Methods.collect, - serializer: IdentitySerializer(), - deserializer: IdentityDeserializer(), - options: .defaults - ) { response in - let message = try response.message - XCTAssertEqual(message, [3, 1, 4, 1, 5]) - } - - let task = Task { - try await client.clientStreaming( - request: ClientRequest.Stream { writer in - try await Task.sleep(for: .seconds(5)) - }, - descriptor: BinaryEcho.Methods.collect, - serializer: IdentitySerializer(), - deserializer: IdentityDeserializer(), - options: .defaults - ) { response in - XCTAssertRejected(response) { error in - XCTAssertEqual(error.code, .unknown) - } - } - } - - task.cancel() - try await task.value - group.cancelAll() - } - } - - func testRunStoppedClient() async throws { - let (_, clientTransport) = InProcessTransport.makePair() - let client = GRPCClient(transport: clientTransport) - // Run the client. - let task = Task { try await client.run() } - task.cancel() - try await task.value - - // Client is stopped, should throw an error. - await XCTAssertThrowsErrorAsync(ofType: RuntimeError.self) { - try await client.run() - } errorHandler: { error in - XCTAssertEqual(error.code, .clientIsStopped) - } - } - - func testRunAlreadyRunningClient() async throws { - let (_, clientTransport) = InProcessTransport.makePair() - let client = GRPCClient(transport: clientTransport) - // Run the client. - let task = Task { try await client.run() } - // Make sure the client is run for the first time here. - try await Task.sleep(for: .milliseconds(10)) - - // Client is already running, should throw an error. - await XCTAssertThrowsErrorAsync(ofType: RuntimeError.self) { - try await client.run() - } errorHandler: { error in - XCTAssertEqual(error.code, .clientIsAlreadyRunning) - } - - task.cancel() - } -} diff --git a/Tests/GRPCCoreTests/GRPCServerTests.swift b/Tests/GRPCCoreTests/GRPCServerTests.swift deleted file mode 100644 index d8771d81e..000000000 --- a/Tests/GRPCCoreTests/GRPCServerTests.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. - */ - -import GRPCCore -import GRPCInProcessTransport -import XCTest - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -final class GRPCServerTests: XCTestCase { - func withInProcessClientConnectedToServer( - services: [any RegistrableRPCService], - interceptors: [any ServerInterceptor] = [], - _ body: (InProcessClientTransport, GRPCServer) async throws -> Void - ) async throws { - let inProcess = InProcessTransport.makePair() - let server = GRPCServer( - transport: inProcess.server, - services: services, - interceptors: interceptors - ) - - try await withThrowingTaskGroup(of: Void.self) { group in - group.addTask { - try await server.serve() - } - - group.addTask { - try await inProcess.client.connect() - } - - try await body(inProcess.client, server) - inProcess.client.beginGracefulShutdown() - server.beginGracefulShutdown() - } - } - - func testServerHandlesUnary() async throws { - try await self.withInProcessClientConnectedToServer(services: [BinaryEcho()]) { client, _ in - try await client.withStream( - descriptor: BinaryEcho.Methods.get, - options: .defaults - ) { stream in - try await stream.outbound.write(.metadata([:])) - try await stream.outbound.write(.message([3, 1, 4, 1, 5])) - await stream.outbound.finish() - - var responseParts = stream.inbound.makeAsyncIterator() - let metadata = try await responseParts.next() - XCTAssertMetadata(metadata) - - let message = try await responseParts.next() - XCTAssertMessage(message) { - XCTAssertEqual($0, [3, 1, 4, 1, 5]) - } - - let status = try await responseParts.next() - XCTAssertStatus(status) { status, _ in - XCTAssertEqual(status.code, .ok) - } - } - } - } - - func testServerHandlesClientStreaming() async throws { - try await self.withInProcessClientConnectedToServer(services: [BinaryEcho()]) { client, _ in - try await client.withStream( - descriptor: BinaryEcho.Methods.collect, - options: .defaults - ) { stream in - try await stream.outbound.write(.metadata([:])) - try await stream.outbound.write(.message([3])) - try await stream.outbound.write(.message([1])) - try await stream.outbound.write(.message([4])) - try await stream.outbound.write(.message([1])) - try await stream.outbound.write(.message([5])) - await stream.outbound.finish() - - var responseParts = stream.inbound.makeAsyncIterator() - let metadata = try await responseParts.next() - XCTAssertMetadata(metadata) - - let message = try await responseParts.next() - XCTAssertMessage(message) { - XCTAssertEqual($0, [3, 1, 4, 1, 5]) - } - - let status = try await responseParts.next() - XCTAssertStatus(status) { status, _ in - XCTAssertEqual(status.code, .ok) - } - } - } - } - - func testServerHandlesServerStreaming() async throws { - try await self.withInProcessClientConnectedToServer(services: [BinaryEcho()]) { client, _ in - try await client.withStream( - descriptor: BinaryEcho.Methods.expand, - options: .defaults - ) { stream in - try await stream.outbound.write(.metadata([:])) - try await stream.outbound.write(.message([3, 1, 4, 1, 5])) - await stream.outbound.finish() - - var responseParts = stream.inbound.makeAsyncIterator() - let metadata = try await responseParts.next() - XCTAssertMetadata(metadata) - - for byte in [3, 1, 4, 1, 5] as [UInt8] { - let message = try await responseParts.next() - XCTAssertMessage(message) { - XCTAssertEqual($0, [byte]) - } - } - - let status = try await responseParts.next() - XCTAssertStatus(status) { status, _ in - XCTAssertEqual(status.code, .ok) - } - } - } - } - - func testServerHandlesBidirectionalStreaming() async throws { - try await self.withInProcessClientConnectedToServer(services: [BinaryEcho()]) { client, _ in - try await client.withStream( - descriptor: BinaryEcho.Methods.update, - options: .defaults - ) { stream in - try await stream.outbound.write(.metadata([:])) - for byte in [3, 1, 4, 1, 5] as [UInt8] { - try await stream.outbound.write(.message([byte])) - } - await stream.outbound.finish() - - var responseParts = stream.inbound.makeAsyncIterator() - let metadata = try await responseParts.next() - XCTAssertMetadata(metadata) - - for byte in [3, 1, 4, 1, 5] as [UInt8] { - let message = try await responseParts.next() - XCTAssertMessage(message) { - XCTAssertEqual($0, [byte]) - } - } - - let status = try await responseParts.next() - XCTAssertStatus(status) { status, _ in - XCTAssertEqual(status.code, .ok) - } - } - } - } - - func testUnimplementedMethod() async throws { - try await self.withInProcessClientConnectedToServer(services: [BinaryEcho()]) { client, _ in - try await client.withStream( - descriptor: MethodDescriptor(service: "not", method: "implemented"), - options: .defaults - ) { stream in - try await stream.outbound.write(.metadata([:])) - await stream.outbound.finish() - - var responseParts = stream.inbound.makeAsyncIterator() - let status = try await responseParts.next() - XCTAssertStatus(status) { status, _ in - XCTAssertEqual(status.code, .unimplemented) - } - } - } - } - - func testMultipleConcurrentRequests() async throws { - try await self.withInProcessClientConnectedToServer(services: [BinaryEcho()]) { client, _ in - await withThrowingTaskGroup(of: Void.self) { group in - for i in UInt8.min ..< UInt8.max { - group.addTask { - try await client.withStream( - descriptor: BinaryEcho.Methods.get, - options: .defaults - ) { stream in - try await stream.outbound.write(.metadata([:])) - try await stream.outbound.write(.message([i])) - await stream.outbound.finish() - - var responseParts = stream.inbound.makeAsyncIterator() - let metadata = try await responseParts.next() - XCTAssertMetadata(metadata) - - let message = try await responseParts.next() - XCTAssertMessage(message) { XCTAssertEqual($0, [i]) } - - let status = try await responseParts.next() - XCTAssertStatus(status) { status, _ in - XCTAssertEqual(status.code, .ok) - } - } - } - } - } - } - } - - func testInterceptorsAreAppliedInOrder() async throws { - let counter1 = AtomicCounter() - let counter2 = AtomicCounter() - - try await self.withInProcessClientConnectedToServer( - services: [BinaryEcho()], - interceptors: [ - .requestCounter(counter1), - .rejectAll(with: RPCError(code: .unavailable, message: "")), - .requestCounter(counter2), - ] - ) { client, _ in - try await client.withStream( - descriptor: BinaryEcho.Methods.get, - options: .defaults - ) { stream in - try await stream.outbound.write(.metadata([:])) - await stream.outbound.finish() - - let parts = try await stream.inbound.collect() - XCTAssertStatus(parts.first) { status, _ in - XCTAssertEqual(status.code, .unavailable) - } - } - } - - XCTAssertEqual(counter1.value, 1) - XCTAssertEqual(counter2.value, 0) - } - - func testInterceptorsAreNotAppliedToUnimplementedMethods() async throws { - let counter = AtomicCounter() - - try await self.withInProcessClientConnectedToServer( - services: [BinaryEcho()], - interceptors: [.requestCounter(counter)] - ) { client, _ in - try await client.withStream( - descriptor: MethodDescriptor(service: "not", method: "implemented"), - options: .defaults - ) { stream in - try await stream.outbound.write(.metadata([:])) - await stream.outbound.finish() - - let parts = try await stream.inbound.collect() - XCTAssertStatus(parts.first) { status, _ in - XCTAssertEqual(status.code, .unimplemented) - } - } - } - - XCTAssertEqual(counter.value, 0) - } - - func testNoNewRPCsAfterServerStopListening() async throws { - try await withInProcessClientConnectedToServer(services: [BinaryEcho()]) { client, server in - // Run an RPC so we know the server is up. - try await self.doEchoGet(using: client) - - // New streams should fail immediately after this. - server.beginGracefulShutdown() - - // RPC should fail now. - await XCTAssertThrowsRPCErrorAsync { - try await client.withStream( - descriptor: BinaryEcho.Methods.get, - options: .defaults - ) { stream in - XCTFail("Stream shouldn't be opened") - } - } errorHandler: { error in - XCTAssertEqual(error.code, .failedPrecondition) - } - } - } - - func testInFlightRPCsCanContinueAfterServerStopListening() async throws { - try await withInProcessClientConnectedToServer(services: [BinaryEcho()]) { client, server in - try await client.withStream( - descriptor: BinaryEcho.Methods.update, - options: .defaults - ) { stream in - try await stream.outbound.write(.metadata([:])) - var iterator = stream.inbound.makeAsyncIterator() - // Don't need to validate the response, just that the server is running. - let metadata = try await iterator.next() - XCTAssertMetadata(metadata) - - // New streams should fail immediately after this. - server.beginGracefulShutdown() - - try await stream.outbound.write(.message([0])) - await stream.outbound.finish() - - let message = try await iterator.next() - XCTAssertMessage(message) { XCTAssertEqual($0, [0]) } - let status = try await iterator.next() - XCTAssertStatus(status) - } - } - } - - func testCancelRunningServer() async throws { - let inProcess = InProcessTransport.makePair() - let task = Task { - let server = GRPCServer(transport: inProcess.server, services: [BinaryEcho()]) - try await server.serve() - } - - try await withThrowingTaskGroup(of: Void.self) { group in - group.addTask { - try? await inProcess.client.connect() - } - - try await self.doEchoGet(using: inProcess.client) - // The server must be running at this point as an RPC has completed. - task.cancel() - try await task.value - - group.cancelAll() - } - } - - func testTestRunStoppedServer() async throws { - let server = GRPCServer(transport: InProcessServerTransport(), services: []) - // Run the server. - let task = Task { try await server.serve() } - task.cancel() - try await task.value - - // Server is stopped, should throw an error. - await XCTAssertThrowsErrorAsync(ofType: RuntimeError.self) { - try await server.serve() - } errorHandler: { error in - XCTAssertEqual(error.code, .serverIsStopped) - } - } - - func testRunServerWhenTransportThrows() async throws { - let server = GRPCServer(transport: ThrowOnRunServerTransport(), services: []) - await XCTAssertThrowsErrorAsync(ofType: RuntimeError.self) { - try await server.serve() - } errorHandler: { error in - XCTAssertEqual(error.code, .transportError) - } - } - - private func doEchoGet(using transport: some ClientTransport) async throws { - try await transport.withStream( - descriptor: BinaryEcho.Methods.get, - options: .defaults - ) { stream in - try await stream.outbound.write(.metadata([:])) - try await stream.outbound.write(.message([0])) - await stream.outbound.finish() - // Don't need to validate the response, just that the server is running. - let parts = try await stream.inbound.collect() - XCTAssertEqual(parts.count, 3) - } - } -} diff --git a/Tests/GRPCCoreTests/Internal/Metadata+GRPCTests.swift b/Tests/GRPCCoreTests/Internal/Metadata+GRPCTests.swift deleted file mode 100644 index 25ded0048..000000000 --- a/Tests/GRPCCoreTests/Internal/Metadata+GRPCTests.swift +++ /dev/null @@ -1,87 +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. - */ - -import XCTest - -@testable import GRPCCore - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -final class MetadataGRPCTests: XCTestCase { - func testPreviousRPCAttemptsValidValues() { - let testData = [("0", 0), ("1", 1), ("-1", -1)] - for (value, expected) in testData { - let metadata: Metadata = ["grpc-previous-rpc-attempts": "\(value)"] - XCTAssertEqual(metadata.previousRPCAttempts, expected) - } - } - - func testPreviousRPCAttemptsInvalidValues() { - let values = ["foo", "42.0"] - for value in values { - let metadata: Metadata = ["grpc-previous-rpc-attempts": "\(value)"] - XCTAssertNil(metadata.previousRPCAttempts) - } - } - - func testSetPreviousRPCAttemptsToValue() { - var metadata: Metadata = [:] - - metadata.previousRPCAttempts = 42 - XCTAssertEqual(metadata, ["grpc-previous-rpc-attempts": "42"]) - - metadata.previousRPCAttempts = nil - XCTAssertEqual(metadata, [:]) - - for i in 0 ..< 5 { - metadata.addString("\(i)", forKey: "grpc-previous-rpc-attempts") - } - XCTAssertEqual(metadata.count, 5) - - // Should remove old values. - metadata.previousRPCAttempts = 42 - XCTAssertEqual(metadata, ["grpc-previous-rpc-attempts": "42"]) - } - - func testRetryPushbackValidDelay() { - let testData: [(String, Duration)] = [ - ("0", .zero), - ("1", Duration(secondsComponent: 0, attosecondsComponent: 1_000_000_000_000_000)), - ("999", Duration(secondsComponent: 0, attosecondsComponent: 999_000_000_000_000_000)), - ("1000", Duration(secondsComponent: 1, attosecondsComponent: 0)), - ("1001", Duration(secondsComponent: 1, attosecondsComponent: 1_000_000_000_000_000)), - ("1999", Duration(secondsComponent: 1, attosecondsComponent: 999_000_000_000_000_000)), - ] - - for (value, expectedDuration) in testData { - let metadata: Metadata = ["grpc-retry-pushback-ms": "\(value)"] - XCTAssertEqual(metadata.retryPushback, .retryAfter(expectedDuration)) - } - } - - func testRetryPushbackInvalidDelay() { - let testData: [String] = ["-1", "-inf", "not-a-number", "42.0"] - - for value in testData { - let metadata: Metadata = ["grpc-retry-pushback-ms": "\(value)"] - XCTAssertEqual(metadata.retryPushback, .stopRetrying) - } - } - - func testRetryPushbackNoValuePresent() { - let metadata: Metadata = [:] - XCTAssertNil(metadata.retryPushback) - } -} diff --git a/Tests/GRPCCoreTests/Internal/MethodConfigsTests.swift b/Tests/GRPCCoreTests/Internal/MethodConfigsTests.swift deleted file mode 100644 index 58ddcde4f..000000000 --- a/Tests/GRPCCoreTests/Internal/MethodConfigsTests.swift +++ /dev/null @@ -1,91 +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. - */ -import GRPCCore -import XCTest - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -final class MethodConfigsTests: XCTestCase { - func testGetConfigurationForKnownMethod() async throws { - let policy = HedgingPolicy( - maxAttempts: 10, - hedgingDelay: .seconds(1), - nonFatalStatusCodes: [] - ) - let defaultConfiguration = MethodConfig(names: [], executionPolicy: .hedge(policy)) - var configurations = MethodConfigs() - configurations.setDefaultConfig(defaultConfiguration) - let descriptor = MethodDescriptor(service: "test", method: "first") - let retryPolicy = RetryPolicy( - maxAttempts: 10, - initialBackoff: .seconds(1), - maxBackoff: .seconds(1), - backoffMultiplier: 1.0, - retryableStatusCodes: [.unavailable] - ) - let overrideConfiguration = MethodConfig(names: [], executionPolicy: .retry(retryPolicy)) - configurations[descriptor] = overrideConfiguration - - XCTAssertEqual(configurations[descriptor], overrideConfiguration) - } - - func testGetConfigurationForUnknownMethodButServiceOverride() { - let policy = HedgingPolicy( - maxAttempts: 10, - hedgingDelay: .seconds(1), - nonFatalStatusCodes: [] - ) - let defaultConfiguration = MethodConfig(names: [], executionPolicy: .hedge(policy)) - var configurations = MethodConfigs() - configurations.setDefaultConfig(defaultConfiguration) - let firstDescriptor = MethodDescriptor(service: "test", method: "") - let retryPolicy = RetryPolicy( - maxAttempts: 10, - initialBackoff: .seconds(1), - maxBackoff: .seconds(1), - backoffMultiplier: 1.0, - retryableStatusCodes: [.unavailable] - ) - let overrideConfiguration = MethodConfig(names: [], executionPolicy: .retry(retryPolicy)) - configurations[firstDescriptor] = overrideConfiguration - - let secondDescriptor = MethodDescriptor(service: "test", method: "second") - XCTAssertEqual(configurations[secondDescriptor], overrideConfiguration) - } - - func testGetConfigurationForUnknownMethodDefaultValue() { - let policy = HedgingPolicy( - maxAttempts: 10, - hedgingDelay: .seconds(1), - nonFatalStatusCodes: [] - ) - let defaultConfiguration = MethodConfig(names: [], executionPolicy: .hedge(policy)) - var configurations = MethodConfigs() - configurations.setDefaultConfig(defaultConfiguration) - let firstDescriptor = MethodDescriptor(service: "test1", method: "first") - let retryPolicy = RetryPolicy( - maxAttempts: 10, - initialBackoff: .seconds(1), - maxBackoff: .seconds(1), - backoffMultiplier: 1.0, - retryableStatusCodes: [.unavailable] - ) - let overrideConfiguration = MethodConfig(names: [], executionPolicy: .retry(retryPolicy)) - configurations[firstDescriptor] = overrideConfiguration - - let secondDescriptor = MethodDescriptor(service: "test2", method: "second") - XCTAssertEqual(configurations[secondDescriptor], defaultConfiguration) - } -} diff --git a/Tests/GRPCCoreTests/Internal/Result+CatchingTests.swift b/Tests/GRPCCoreTests/Internal/Result+CatchingTests.swift deleted file mode 100644 index aee39daf4..000000000 --- a/Tests/GRPCCoreTests/Internal/Result+CatchingTests.swift +++ /dev/null @@ -1,66 +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. - */ - -import XCTest - -@testable import GRPCCore - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -final class ResultCatchingTests: XCTestCase { - func testResultCatching() async { - let result = await Result { - try? await Task.sleep(nanoseconds: 1) - throw RPCError(code: .unknown, message: "foo") - } - - switch result { - case .success: - XCTFail() - case .failure(let error): - XCTAssertEqual(error as? RPCError, RPCError(code: .unknown, message: "foo")) - } - } - - func testCastToErrorOfCorrectType() async { - let result = Result.failure(RPCError(code: .unknown, message: "foo")) - let typedFailure = result.castError(to: RPCError.self) { _ in - XCTFail("buildError(_:) was called") - return RPCError(code: .failedPrecondition, message: "shouldn't happen") - } - - switch typedFailure { - case .success: - XCTFail() - case .failure(let error): - XCTAssertEqual(error, RPCError(code: .unknown, message: "foo")) - } - } - - func testCastToErrorOfIncorrectType() async { - struct WrongError: Error {} - let result = Result.failure(WrongError()) - let typedFailure = result.castError(to: RPCError.self) { _ in - return RPCError(code: .invalidArgument, message: "fallback") - } - - switch typedFailure { - case .success: - XCTFail() - case .failure(let error): - XCTAssertEqual(error, RPCError(code: .invalidArgument, message: "fallback")) - } - } -} diff --git a/Tests/GRPCCoreTests/MetadataTests.swift b/Tests/GRPCCoreTests/MetadataTests.swift deleted file mode 100644 index f0b29df04..000000000 --- a/Tests/GRPCCoreTests/MetadataTests.swift +++ /dev/null @@ -1,255 +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. - */ - -import GRPCCore -import Testing - -@Suite("Metadata") -struct MetadataTests { - @Test("Initialize from Sequence") - func initFromSequence() { - let elements: [Metadata.Element] = [ - (key: "key1", value: "value1"), - (key: "key2", value: "value2"), - (key: "key3", value: "value3"), - ] - - let metadata = Metadata(elements) - let expected: Metadata = ["key1": "value1", "key2": "value2", "key3": "value3"] - #expect(metadata == expected) - } - - @Test("Add string Value") - func addStringValue() { - var metadata = Metadata() - #expect(metadata.isEmpty) - - metadata.addString("testValue", forKey: "testString") - #expect(metadata.count == 1) - - let sequence = metadata[stringValues: "testString"] - var iterator = sequence.makeIterator() - #expect(iterator.next() == "testValue") - #expect(iterator.next() == nil) - } - - @Test("Add binary value") - func addBinaryValue() { - var metadata = Metadata() - #expect(metadata.isEmpty) - - metadata.addBinary(Array("base64encodedString".utf8), forKey: "testBinary-bin") - #expect(metadata.count == 1) - - let sequence = metadata[binaryValues: "testBinary-bin"] - var iterator = sequence.makeIterator() - #expect(iterator.next() == Array("base64encodedString".utf8)) - #expect(iterator.next() == nil) - } - - @Test("Initialize from dictionary literal") - func initFromDictionaryLiteral() { - let metadata: Metadata = [ - "testKey": "stringValue", - "testKey-bin": .binary(Array("base64encodedString".utf8)), - ] - #expect(metadata.count == 2) - - let stringSequence = metadata[stringValues: "testKey"] - var stringIterator = stringSequence.makeIterator() - #expect(stringIterator.next() == "stringValue") - #expect(stringIterator.next() == nil) - - let binarySequence = metadata[binaryValues: "testKey-bin"] - var binaryIterator = binarySequence.makeIterator() - #expect(binaryIterator.next() == Array("base64encodedString".utf8)) - #expect(binaryIterator.next() == nil) - } - - @Suite("Replace or add value") - struct ReplaceOrAdd { - @Suite("String") - struct StringValues { - var metadata: Metadata = [ - "key1": "value1", - "key1": "value2", - ] - - @Test("Add different key") - mutating func addNewKey() async throws { - self.metadata.replaceOrAddString("value3", forKey: "key2") - #expect(Array(self.metadata[stringValues: "key1"]) == ["value1", "value2"]) - #expect(Array(self.metadata[stringValues: "key2"]) == ["value3"]) - #expect(self.metadata.count == 3) - } - - @Test("Replace values for existing key") - mutating func replaceValues() async throws { - self.metadata.replaceOrAddString("value3", forKey: "key1") - #expect(Array(self.metadata[stringValues: "key1"]) == ["value3"]) - #expect(self.metadata.count == 1) - } - } - - @Suite("Binary") - struct BinaryValues { - var metadata: Metadata = [ - "key1-bin": [0], - "key1-bin": [1], - ] - - @Test("Add different key") - mutating func addNewKey() async throws { - self.metadata.replaceOrAddBinary([2], forKey: "key2-bin") - #expect(Array(self.metadata[binaryValues: "key1-bin"]) == [[0], [1]]) - #expect(Array(self.metadata[binaryValues: "key2-bin"]) == [[2]]) - #expect(self.metadata.count == 3) - } - - @Test("Replace values for existing key") - mutating func replaceValues() async throws { - self.metadata.replaceOrAddBinary([2], forKey: "key1-bin") - #expect(Array(self.metadata[binaryValues: "key1-bin"]) == [[2]]) - #expect(self.metadata.count == 1) - } - } - } - - @Test("Reserve more capacity increases capacity") - func reserveMoreCapacity() { - var metadata = Metadata() - #expect(metadata.capacity == 0) - - metadata.reserveCapacity(10) - #expect(metadata.capacity == 10) - } - - @Test("Reserve less capacity doesn't reduce capacity") - func reserveCapacity() { - var metadata = Metadata() - #expect(metadata.capacity == 0) - metadata.reserveCapacity(10) - #expect(metadata.capacity == 10) - metadata.reserveCapacity(0) - #expect(metadata.capacity == 10) - } - - @Test("Iterate over all values for a key") - func iterateOverValuesForKey() { - let metadata: Metadata = [ - "key-bin": "1", - "key-bin": [1], - "key-bin": "2", - "key-bin": [2], - "key-bin": "3", - "key-bin": [3], - ] - - #expect(Array(metadata["key-bin"]) == ["1", [1], "2", [2], "3", [3]]) - } - - @Test("Iterate over string values for a key") - func iterateOverStringsForKey() { - let metadata: Metadata = [ - "key-bin": "1", - "key-bin": [1], - "key-bin": "2", - "key-bin": [2], - "key-bin": "3", - "key-bin": [3], - ] - - #expect(Array(metadata[stringValues: "key-bin"]) == ["1", "2", "3"]) - } - - @Test("Iterate over binary values for a key") - func iterateOverBinaryForKey() { - let metadata: Metadata = [ - "key-bin": "1", - "key-bin": [1], - "key-bin": "2", - "key-bin": [2], - "key-bin": "3", - "key-bin": [3], - ] - - #expect(Array(metadata[binaryValues: "key-bin"]) == [[1], [2], [3]]) - } - - @Test("Iterate over base64 encoded binary values for a key") - func iterateOverBase64BinaryEncodedValuesForKey() { - let metadata: Metadata = [ - "key-bin": "c3RyaW5nMQ==", - "key-bin": .binary(.init("data1".utf8)), - "key-bin": "c3RyaW5nMg==", - "key-bin": .binary(.init("data2".utf8)), - "key-bin": "c3RyaW5nMw==", - "key-bin": .binary(.init("data3".utf8)), - ] - - let expected: [[UInt8]] = [ - Array("string1".utf8), - Array("data1".utf8), - Array("string2".utf8), - Array("data2".utf8), - Array("string3".utf8), - Array("data3".utf8), - ] - - #expect(Array(metadata[binaryValues: "key-bin"]) == expected) - } - - @Test("Subscripts are case-insensitive") - func subscriptIsCaseInsensitive() { - let metadata: Metadata = [ - "key1": "value1", - "KEY2": "value2", - ] - - #expect(Array(metadata[stringValues: "key1"]) == ["value1"]) - #expect(Array(metadata[stringValues: "KEY1"]) == ["value1"]) - - #expect(Array(metadata[stringValues: "key2"]) == ["value2"]) - #expect(Array(metadata[stringValues: "KEY2"]) == ["value2"]) - } - - @Suite("Remove all") - struct RemoveAll { - var metadata: Metadata = [ - "key1": "value1", - "key2": "value2", - "key3": "value1", - ] - - @Test("Where value matches") - mutating func removeAllWhereValueMatches() async throws { - self.metadata.removeAll { _, value in - value == "value1" - } - - #expect(self.metadata == ["key2": "value2"]) - } - - @Test("Where key matches") - mutating func removeAllWhereKeyMatches() async throws { - self.metadata.removeAll { key, _ in - key == "key2" - } - - #expect(self.metadata == ["key1": "value1", "key3": "value1"]) - } - } -} diff --git a/Tests/GRPCCoreTests/MethodDescriptorTests.swift b/Tests/GRPCCoreTests/MethodDescriptorTests.swift deleted file mode 100644 index cf4568898..000000000 --- a/Tests/GRPCCoreTests/MethodDescriptorTests.swift +++ /dev/null @@ -1,26 +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. - */ -import GRPCCore -import XCTest - -final class MethodDescriptorTests: XCTestCase { - func testFullyQualifiedName() { - let descriptor = MethodDescriptor(service: "foo.bar", method: "Baz") - XCTAssertEqual(descriptor.service, "foo.bar") - XCTAssertEqual(descriptor.method, "Baz") - XCTAssertEqual(descriptor.fullyQualifiedMethod, "foo.bar/Baz") - } -} diff --git a/Tests/GRPCCoreTests/RPCErrorTests.swift b/Tests/GRPCCoreTests/RPCErrorTests.swift deleted file mode 100644 index dc65122b0..000000000 --- a/Tests/GRPCCoreTests/RPCErrorTests.swift +++ /dev/null @@ -1,127 +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. - */ -import GRPCCore -import XCTest - -final class RPCErrorTests: XCTestCase { - private static let statusCodeRawValue: [(RPCError.Code, Int)] = [ - (.cancelled, 1), - (.unknown, 2), - (.invalidArgument, 3), - (.deadlineExceeded, 4), - (.notFound, 5), - (.alreadyExists, 6), - (.permissionDenied, 7), - (.resourceExhausted, 8), - (.failedPrecondition, 9), - (.aborted, 10), - (.outOfRange, 11), - (.unimplemented, 12), - (.internalError, 13), - (.unavailable, 14), - (.dataLoss, 15), - (.unauthenticated, 16), - ] - - func testCustomStringConvertible() { - XCTAssertDescription(RPCError(code: .dataLoss, message: ""), #"dataLoss: """#) - XCTAssertDescription(RPCError(code: .unknown, message: "message"), #"unknown: "message""#) - XCTAssertDescription(RPCError(code: .aborted, message: "message"), #"aborted: "message""#) - - struct TestError: Error {} - XCTAssertDescription( - RPCError(code: .aborted, message: "message", cause: TestError()), - #"aborted: "message" (cause: "TestError()")"# - ) - } - - func testErrorFromStatus() throws { - var status = Status(code: .ok, message: "") - // ok isn't an error - XCTAssertNil(RPCError(status: status)) - - status.code = .invalidArgument - var error = try XCTUnwrap(RPCError(status: status)) - XCTAssertEqual(error.code, .invalidArgument) - XCTAssertEqual(error.message, "") - XCTAssertEqual(error.metadata, [:]) - - status.code = .cancelled - status.message = "an error message" - error = try XCTUnwrap(RPCError(status: status)) - XCTAssertEqual(error.code, .cancelled) - XCTAssertEqual(error.message, "an error message") - XCTAssertEqual(error.metadata, [:]) - } - - func testErrorCodeFromStatusCode() throws { - XCTAssertNil(RPCError.Code(Status.Code.ok)) - XCTAssertEqual(RPCError.Code(Status.Code.cancelled), .cancelled) - XCTAssertEqual(RPCError.Code(Status.Code.unknown), .unknown) - XCTAssertEqual(RPCError.Code(Status.Code.invalidArgument), .invalidArgument) - XCTAssertEqual(RPCError.Code(Status.Code.deadlineExceeded), .deadlineExceeded) - XCTAssertEqual(RPCError.Code(Status.Code.notFound), .notFound) - XCTAssertEqual(RPCError.Code(Status.Code.alreadyExists), .alreadyExists) - XCTAssertEqual(RPCError.Code(Status.Code.permissionDenied), .permissionDenied) - XCTAssertEqual(RPCError.Code(Status.Code.resourceExhausted), .resourceExhausted) - XCTAssertEqual(RPCError.Code(Status.Code.failedPrecondition), .failedPrecondition) - XCTAssertEqual(RPCError.Code(Status.Code.aborted), .aborted) - XCTAssertEqual(RPCError.Code(Status.Code.outOfRange), .outOfRange) - XCTAssertEqual(RPCError.Code(Status.Code.unimplemented), .unimplemented) - XCTAssertEqual(RPCError.Code(Status.Code.internalError), .internalError) - XCTAssertEqual(RPCError.Code(Status.Code.unavailable), .unavailable) - XCTAssertEqual(RPCError.Code(Status.Code.dataLoss), .dataLoss) - XCTAssertEqual(RPCError.Code(Status.Code.unauthenticated), .unauthenticated) - } - - func testEquatableConformance() { - XCTAssertEqual( - RPCError(code: .cancelled, message: ""), - RPCError(code: .cancelled, message: "") - ) - - XCTAssertEqual( - RPCError(code: .cancelled, message: "message"), - RPCError(code: .cancelled, message: "message") - ) - - XCTAssertEqual( - RPCError(code: .cancelled, message: "message", metadata: ["foo": "bar"]), - RPCError(code: .cancelled, message: "message", metadata: ["foo": "bar"]) - ) - - XCTAssertNotEqual( - RPCError(code: .cancelled, message: ""), - RPCError(code: .cancelled, message: "message") - ) - - XCTAssertNotEqual( - RPCError(code: .cancelled, message: "message"), - RPCError(code: .unknown, message: "message") - ) - - XCTAssertNotEqual( - RPCError(code: .cancelled, message: "message", metadata: ["foo": "bar"]), - RPCError(code: .cancelled, message: "message", metadata: ["foo": "baz"]) - ) - } - - func testStatusCodeRawValues() { - for (code, expected) in Self.statusCodeRawValue { - XCTAssertEqual(code.rawValue, expected, "\(code) had unexpected raw value") - } - } -} diff --git a/Tests/GRPCCoreTests/RPCPartsTests.swift b/Tests/GRPCCoreTests/RPCPartsTests.swift deleted file mode 100644 index e950a8e97..000000000 --- a/Tests/GRPCCoreTests/RPCPartsTests.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. - */ -import GRPCCore -import XCTest - -final class RPCPartsTests: XCTestCase { - func testPartsFitInExistentialContainer() { - XCTAssertLessThanOrEqual(MemoryLayout.size, 24) - XCTAssertLessThanOrEqual(MemoryLayout.size, 24) - } -} diff --git a/Tests/GRPCCoreTests/RuntimeErrorTests.swift b/Tests/GRPCCoreTests/RuntimeErrorTests.swift deleted file mode 100644 index 2d30f96f0..000000000 --- a/Tests/GRPCCoreTests/RuntimeErrorTests.swift +++ /dev/null @@ -1,54 +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. - */ -import GRPCCore -import XCTest - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -final class RuntimeErrorTests: XCTestCase { - func testCopyOnWrite() { - // RuntimeError has a heap based storage, so check CoW semantics are correctly implemented. - let error1 = RuntimeError(code: .transportError, message: "Failed to start transport") - var error2 = error1 - error2.code = .serverIsAlreadyRunning - XCTAssertEqual(error1.code, .transportError) - XCTAssertEqual(error2.code, .serverIsAlreadyRunning) - - var error3 = error1 - error3.message = "foo" - XCTAssertEqual(error1.message, "Failed to start transport") - XCTAssertEqual(error3.message, "foo") - - var error4 = error1 - error4.cause = CancellationError() - XCTAssertNil(error1.cause) - XCTAssert(error4.cause is CancellationError) - } - - func testCustomStringConvertible() { - let error1 = RuntimeError(code: .transportError, message: "Failed to start transport") - XCTAssertDescription(error1, #"transportError: "Failed to start transport""#) - - let error2 = RuntimeError( - code: .transportError, - message: "Failed to start transport", - cause: CancellationError() - ) - XCTAssertDescription( - error2, - #"transportError: "Failed to start transport" (cause: "CancellationError()")"# - ) - } -} diff --git a/Tests/GRPCCoreTests/ServiceDescriptorTests.swift b/Tests/GRPCCoreTests/ServiceDescriptorTests.swift deleted file mode 100644 index 0adfe524a..000000000 --- a/Tests/GRPCCoreTests/ServiceDescriptorTests.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 GRPCCore -import XCTest - -final class ServiceDescriptorTests: XCTestCase { - func testFullyQualifiedName() { - let descriptor = ServiceDescriptor(package: "foo.bar", service: "Baz") - XCTAssertEqual(descriptor.package, "foo.bar") - XCTAssertEqual(descriptor.service, "Baz") - XCTAssertEqual(descriptor.fullyQualifiedService, "foo.bar.Baz") - } -} diff --git a/Tests/GRPCCoreTests/StatusTests.swift b/Tests/GRPCCoreTests/StatusTests.swift deleted file mode 100644 index 936ff8e41..000000000 --- a/Tests/GRPCCoreTests/StatusTests.swift +++ /dev/null @@ -1,72 +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. - */ - -import GRPCCore -import Testing - -@Suite("Status") -struct StatusTests { - @Suite("Code") - struct Code { - @Test("rawValue", arguments: zip(Status.Code.all, 0 ... 16)) - func rawValueOfStatusCodes(code: Status.Code, expected: Int) { - #expect(code.rawValue == expected) - } - - @Test( - "Initialize from RPCError.Code", - arguments: zip( - RPCError.Code.all, - Status.Code.all.dropFirst() // Drop '.ok', there is no '.ok' error code. - ) - ) - func initFromRPCErrorCode(errorCode: RPCError.Code, expected: Status.Code) { - #expect(Status.Code(errorCode) == expected) - } - - @Test("Initialize from rawValue", arguments: zip(0 ... 16, Status.Code.all)) - func initFromRawValue(rawValue: Int, expected: Status.Code) { - #expect(Status.Code(rawValue: rawValue) == expected) - } - - @Test("Initialize from invalid rawValue", arguments: [-1, 17, 100, .max]) - func initFromInvalidRawValue(rawValue: Int) { - #expect(Status.Code(rawValue: rawValue) == nil) - } - } - - @Test("CustomStringConvertible conformance") - func customStringConvertible() { - #expect("\(Status(code: .ok, message: ""))" == #"ok: """#) - #expect("\(Status(code: .dataLoss, message: "oh no"))" == #"dataLoss: "oh no""#) - } - - @Test("Equatable conformance") - func equatable() { - let ok = Status(code: .ok, message: "") - let okWithMessage = Status(code: .ok, message: "message") - let internalError = Status(code: .internalError, message: "") - - #expect(ok == ok) - #expect(ok != okWithMessage) - #expect(ok != internalError) - } - - @Test("Fits in existential container") - func fitsInExistentialContainer() { - #expect(MemoryLayout.size <= 24) - } -} diff --git a/Tests/GRPCCoreTests/Streaming/Internal/AsyncSequenceOfOne.swift b/Tests/GRPCCoreTests/Streaming/Internal/AsyncSequenceOfOne.swift deleted file mode 100644 index 648e935e9..000000000 --- a/Tests/GRPCCoreTests/Streaming/Internal/AsyncSequenceOfOne.swift +++ /dev/null @@ -1,43 +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. - */ - -import XCTest - -@testable import GRPCCore - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -internal final class AsyncSequenceOfOneTests: XCTestCase { - func testSuccessPath() async throws { - let sequence = RPCAsyncSequence.one("foo") - let contents = try await sequence.collect() - XCTAssertEqual(contents, ["foo"]) - } - - func testFailurePath() async throws { - let error = RPCError(code: .cancelled, message: "foo") - let sequence = RPCAsyncSequence.throwing(error) - - do { - let _ = try await sequence.collect() - XCTFail("Expected an error to be thrown") - } catch let error as RPCError { - XCTAssertEqual(error.code, .cancelled) - XCTAssertEqual(error.message, "foo") - } catch { - XCTFail("Expected error of type RPCError to be thrown") - } - } -} diff --git a/Tests/GRPCCoreTests/Streaming/Internal/BroadcastAsyncSequenceTests.swift b/Tests/GRPCCoreTests/Streaming/Internal/BroadcastAsyncSequenceTests.swift deleted file mode 100644 index 6195977c6..000000000 --- a/Tests/GRPCCoreTests/Streaming/Internal/BroadcastAsyncSequenceTests.swift +++ /dev/null @@ -1,257 +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. - */ - -import XCTest - -@testable import GRPCCore - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -final class BroadcastAsyncSequenceTests: XCTestCase { - func testSingleSubscriberToEmptyStream() async throws { - let (stream, source) = BroadcastAsyncSequence.makeStream(of: Int.self, bufferSize: 16) - source.finish() - let elements = try await stream.collect() - XCTAssertEqual(elements, []) - } - - func testMultipleSubscribersToEmptyStream() async throws { - let (stream, source) = BroadcastAsyncSequence.makeStream(of: Int.self, bufferSize: 16) - source.finish() - do { - let elements = try await stream.collect() - XCTAssertEqual(elements, []) - } - do { - let elements = try await stream.collect() - XCTAssertEqual(elements, []) - } - } - - func testSubscribeToEmptyStreamBeforeFinish() async throws { - let (stream, source) = BroadcastAsyncSequence.makeStream(of: Int.self, bufferSize: 16) - var iterator = stream.makeAsyncIterator() - source.finish() - let element = try await iterator.next() - XCTAssertNil(element) - } - - func testSlowConsumerIsLeftBehind() async throws { - let (stream, source) = BroadcastAsyncSequence.makeStream(of: Int.self, bufferSize: 16) - var consumer1 = stream.makeAsyncIterator() - var consumer2 = stream.makeAsyncIterator() - - for element in 0 ..< 15 { - try await source.write(element) - } - - // Buffer should now be full. Consume with one consumer so that the other is dropped on - // the next yield. - let element = try await consumer1.next() - XCTAssertEqual(element, 0) - - // Will invalidate consumer2 as the slowest consumer. - try await source.write(15) - - await XCTAssertThrowsErrorAsync { - try await consumer2.next() - } errorHandler: { error in - XCTAssertEqual(error as? BroadcastAsyncSequenceError, .consumingTooSlow) - } - - // consumer1 should be free to continue. - for expected in 1 ... 15 { - let element = try await consumer1.next() - XCTAssertEqual(element, expected) - } - - // consumer1 should end as expected. - source.finish() - let end = try await consumer1.next() - XCTAssertNil(end) - } - - func testConsumerJoiningAfterSomeElements() async throws { - let (stream, source) = BroadcastAsyncSequence.makeStream(of: Int.self, bufferSize: 16) - for element in 0 ..< 10 { - try await source.write(element) - } - - var consumer1 = stream.makeAsyncIterator() - do { - for expected in 0 ..< 8 { - let element = try await consumer1.next() - XCTAssertEqual(element, expected) - } - } - - // Add a second consumer, consume the first four elements. - var consumer2 = stream.makeAsyncIterator() - do { - for expected in 0 ..< 4 { - let element = try await consumer2.next() - XCTAssertEqual(element, expected) - } - } - - // Add another consumer, consume the first two elements. - var consumer3 = stream.makeAsyncIterator() - do { - for expected in 0 ..< 2 { - let element = try await consumer3.next() - XCTAssertEqual(element, expected) - } - } - - // Advance each consumer in lock-step. - for offset in 0 ..< 10 { - try await source.write(10 + offset) - let element1 = try await consumer1.next() - XCTAssertEqual(element1, 8 + offset) - let element2 = try await consumer2.next() - XCTAssertEqual(element2, 4 + offset) - let element3 = try await consumer3.next() - XCTAssertEqual(element3, 2 + offset) - } - - // Subscribing isn't possible. - await XCTAssertThrowsErrorAsync { - try await stream.collect() - } errorHandler: { error in - XCTAssertEqual(error as? BroadcastAsyncSequenceError, .consumingTooSlow) - } - - source.finish() - - // All elements are present. The existing consumers can finish however they choose. - do { - for expected in 18 ..< 20 { - let element = try await consumer1.next() - XCTAssertEqual(element, expected) - } - let end = try await consumer1.next() - XCTAssertNil(end) - } - - do { - for expected in 14 ..< 20 { - let element = try await consumer2.next() - XCTAssertEqual(element, expected) - } - let end = try await consumer2.next() - XCTAssertNil(end) - } - - do { - for expected in 12 ..< 20 { - let element = try await consumer3.next() - XCTAssertEqual(element, expected) - } - let end = try await consumer3.next() - XCTAssertNil(end) - } - } - - func testInvalidateAllConsumersForSingleConcurrentConsumer() async throws { - let (stream, source) = BroadcastAsyncSequence.makeStream(of: Int.self, bufferSize: 16) - for element in 0 ..< 10 { - try await source.write(element) - } - - var consumer1 = stream.makeAsyncIterator() - stream.invalidateAllSubscriptions() - await XCTAssertThrowsErrorAsync { - try await consumer1.next() - } errorHandler: { error in - XCTAssertEqual(error as? BroadcastAsyncSequenceError, .consumingTooSlow) - } - - // Subscribe, consume one, then cancel. - var consumer2 = stream.makeAsyncIterator() - do { - let value = try await consumer2.next() - XCTAssertEqual(value, 0) - } - stream.invalidateAllSubscriptions() - await XCTAssertThrowsErrorAsync { - try await consumer2.next() - } errorHandler: { error in - XCTAssertEqual(error as? BroadcastAsyncSequenceError, .consumingTooSlow) - } - } - - func testInvalidateAllConsumersForMultipleConcurrentConsumer() async throws { - let (stream, source) = BroadcastAsyncSequence.makeStream(of: Int.self, bufferSize: 16) - for element in 0 ..< 10 { - try await source.write(element) - } - - let consumers: [BroadcastAsyncSequence.AsyncIterator] = (0 ..< 5).map { _ in - stream.makeAsyncIterator() - } - - for var consumer in consumers { - let value = try await consumer.next() - XCTAssertEqual(value, 0) - } - - stream.invalidateAllSubscriptions() - - for var consumer in consumers { - await XCTAssertThrowsErrorAsync { - try await consumer.next() - } errorHandler: { error in - XCTAssertEqual(error as? BroadcastAsyncSequenceError, .consumingTooSlow) - } - } - } - - func testCancelSubscriber() async throws { - let (stream, _) = BroadcastAsyncSequence.makeStream(of: Int.self, bufferSize: 16) - await withTaskGroup(of: Void.self) { group in - group.cancelAll() - group.addTask { - do { - _ = try await stream.collect() - XCTFail() - } catch { - XCTAssert(error is CancellationError) - } - } - } - } - - func testCancelProducer() async throws { - let (_, source) = BroadcastAsyncSequence.makeStream(of: Int.self, bufferSize: 16) - for i in 0 ..< 15 { - try await source.write(i) - } - - try await withThrowingTaskGroup(of: Void.self) { group in - group.cancelAll() - for _ in 0 ..< 10 { - group.addTask { - try await source.write(42) - } - } - - while let result = await group.nextResult() { - XCTAssertThrowsError(try result.get()) { error in - XCTAssert(error is CancellationError) - } - } - } - } -} diff --git a/Tests/GRPCCoreTests/Test Utilities/AsyncSequence+Utilities.swift b/Tests/GRPCCoreTests/Test Utilities/AsyncSequence+Utilities.swift deleted file mode 100644 index b82b7185e..000000000 --- a/Tests/GRPCCoreTests/Test Utilities/AsyncSequence+Utilities.swift +++ /dev/null @@ -1,22 +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 AsyncSequence { - func collect() async throws -> [Element] { - return try await self.reduce(into: []) { $0.append($1) } - } -} diff --git a/Tests/GRPCCoreTests/Test Utilities/AtomicCounter.swift b/Tests/GRPCCoreTests/Test Utilities/AtomicCounter.swift deleted file mode 100644 index b9e9fb5b8..000000000 --- a/Tests/GRPCCoreTests/Test Utilities/AtomicCounter.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 Synchronization - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -final class AtomicCounter: Sendable { - private let counter: Atomic - - init(_ initialValue: Int = 0) { - self.counter = Atomic(initialValue) - } - - var value: Int { - self.counter.load(ordering: .sequentiallyConsistent) - } - - @discardableResult - func increment() -> (oldValue: Int, newValue: Int) { - self.counter.add(1, ordering: .sequentiallyConsistent) - } -} diff --git a/Tests/GRPCCoreTests/Test Utilities/Call/Client/ClientInterceptors.swift b/Tests/GRPCCoreTests/Test Utilities/Call/Client/ClientInterceptors.swift deleted file mode 100644 index a45d64fd5..000000000 --- a/Tests/GRPCCoreTests/Test Utilities/Call/Client/ClientInterceptors.swift +++ /dev/null @@ -1,88 +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. - */ - -import GRPCCore - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension ClientInterceptor where Self == RejectAllClientInterceptor { - static func rejectAll(with error: RPCError) -> Self { - return RejectAllClientInterceptor(error: error, throw: false) - } - - static func throwError(_ error: RPCError) -> Self { - return RejectAllClientInterceptor(error: error, throw: true) - } - -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension ClientInterceptor where Self == RequestCountingClientInterceptor { - static func requestCounter(_ counter: AtomicCounter) -> Self { - return RequestCountingClientInterceptor(counter: counter) - } -} - -/// Rejects all RPCs with the provided error. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -struct RejectAllClientInterceptor: ClientInterceptor { - /// The error to reject all RPCs with. - let error: RPCError - /// Whether the error should be thrown. If `false` then the request is rejected with the error - /// instead. - let `throw`: Bool - - init(error: RPCError, throw: Bool = false) { - self.error = error - self.`throw` = `throw` - } - - func intercept( - request: ClientRequest.Stream, - context: ClientContext, - next: ( - ClientRequest.Stream, - ClientContext - ) async throws -> ClientResponse.Stream - ) async throws -> ClientResponse.Stream { - if self.throw { - throw self.error - } else { - return ClientResponse.Stream(error: self.error) - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -struct RequestCountingClientInterceptor: ClientInterceptor { - /// The number of requests made. - let counter: AtomicCounter - - init(counter: AtomicCounter) { - self.counter = counter - } - - func intercept( - request: ClientRequest.Stream, - context: ClientContext, - next: ( - ClientRequest.Stream, - ClientContext - ) async throws -> ClientResponse.Stream - ) async throws -> ClientResponse.Stream { - self.counter.increment() - return try await next(request, context) - } -} diff --git a/Tests/GRPCCoreTests/Test Utilities/Call/Server/ServerInterceptors.swift b/Tests/GRPCCoreTests/Test Utilities/Call/Server/ServerInterceptors.swift deleted file mode 100644 index 9a3dc96c7..000000000 --- a/Tests/GRPCCoreTests/Test Utilities/Call/Server/ServerInterceptors.swift +++ /dev/null @@ -1,87 +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. - */ - -import GRPCCore - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension ServerInterceptor where Self == RejectAllServerInterceptor { - static func rejectAll(with error: RPCError) -> Self { - return RejectAllServerInterceptor(error: error, throw: false) - } - - static func throwError(_ error: RPCError) -> Self { - return RejectAllServerInterceptor(error: error, throw: true) - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension ServerInterceptor where Self == RequestCountingServerInterceptor { - static func requestCounter(_ counter: AtomicCounter) -> Self { - return RequestCountingServerInterceptor(counter: counter) - } -} - -/// Rejects all RPCs with the provided error. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -struct RejectAllServerInterceptor: ServerInterceptor { - /// The error to reject all RPCs with. - let error: RPCError - /// Whether the error should be thrown. If `false` then the request is rejected with the error - /// instead. - let `throw`: Bool - - init(error: RPCError, throw: Bool = false) { - self.error = error - self.`throw` = `throw` - } - - func intercept( - request: ServerRequest.Stream, - context: ServerContext, - next: @Sendable ( - ServerRequest.Stream, - ServerContext - ) async throws -> ServerResponse.Stream - ) async throws -> ServerResponse.Stream { - if self.throw { - throw self.error - } else { - return ServerResponse.Stream(error: self.error) - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -struct RequestCountingServerInterceptor: ServerInterceptor { - /// The number of requests made. - let counter: AtomicCounter - - init(counter: AtomicCounter) { - self.counter = counter - } - - func intercept( - request: ServerRequest.Stream, - context: ServerContext, - next: @Sendable ( - ServerRequest.Stream, - ServerContext - ) async throws -> ServerResponse.Stream - ) async throws -> ServerResponse.Stream { - self.counter.increment() - return try await next(request, context) - } -} diff --git a/Tests/GRPCCoreTests/Test Utilities/Coding+Identity.swift b/Tests/GRPCCoreTests/Test Utilities/Coding+Identity.swift deleted file mode 100644 index 335426fad..000000000 --- a/Tests/GRPCCoreTests/Test Utilities/Coding+Identity.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. - */ -import GRPCCore - -struct IdentitySerializer: MessageSerializer { - func serialize(_ message: [UInt8]) throws -> [UInt8] { - return message - } -} - -struct IdentityDeserializer: MessageDeserializer { - func deserialize(_ serializedMessageBytes: [UInt8]) throws -> [UInt8] { - return serializedMessageBytes - } -} diff --git a/Tests/GRPCCoreTests/Test Utilities/Coding+JSON.swift b/Tests/GRPCCoreTests/Test Utilities/Coding+JSON.swift deleted file mode 100644 index 3008cf3ef..000000000 --- a/Tests/GRPCCoreTests/Test Utilities/Coding+JSON.swift +++ /dev/null @@ -1,42 +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. - */ -import GRPCCore - -import struct Foundation.Data -import class Foundation.JSONDecoder -import class Foundation.JSONEncoder - -struct JSONSerializer: MessageSerializer { - func serialize(_ message: Message) throws -> [UInt8] { - do { - let jsonEncoder = JSONEncoder() - return try Array(jsonEncoder.encode(message)) - } catch { - throw RPCError(code: .internalError, message: "Can't serialize message to JSON. \(error)") - } - } -} - -struct JSONDeserializer: MessageDeserializer { - func deserialize(_ serializedMessageBytes: [UInt8]) throws -> Message { - do { - let jsonDecoder = JSONDecoder() - return try jsonDecoder.decode(Message.self, from: Data(serializedMessageBytes)) - } catch { - throw RPCError(code: .internalError, message: "Can't deserialze message from JSON. \(error)") - } - } -} diff --git a/Tests/GRPCCoreTests/Test Utilities/RPCAsyncSequence+Utilities.swift b/Tests/GRPCCoreTests/Test Utilities/RPCAsyncSequence+Utilities.swift deleted file mode 100644 index b996e3d27..000000000 --- a/Tests/GRPCCoreTests/Test Utilities/RPCAsyncSequence+Utilities.swift +++ /dev/null @@ -1,33 +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. - */ -import GRPCCore - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension RPCAsyncSequence where Failure == any Error { - static func elements(_ elements: Element...) -> Self { - return .elements(elements) - } - - static func elements(_ elements: [Element]) -> Self { - let stream = AsyncThrowingStream { - for element in elements { - $0.yield(element) - } - $0.finish() - } - return RPCAsyncSequence(wrapping: stream) - } -} diff --git a/Tests/GRPCCoreTests/Test Utilities/RPCWriter+Utilities.swift b/Tests/GRPCCoreTests/Test Utilities/RPCWriter+Utilities.swift deleted file mode 100644 index 923ab267d..000000000 --- a/Tests/GRPCCoreTests/Test Utilities/RPCWriter+Utilities.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. - */ -import GRPCCore -import XCTest - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -extension RPCWriter { - /// Returns a writer which calls `XCTFail(_:)` on every write. - static func failTestOnWrite(elementType: Element.Type = Element.self) -> Self { - return RPCWriter(wrapping: FailOnWrite()) - } - - /// Returns a writer which gathers writes into an `AsyncStream`. - static func gathering(into continuation: AsyncStream.Continuation) -> Self { - return RPCWriter(wrapping: AsyncStreamGatheringWriter(continuation: continuation)) - } -} - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -private struct FailOnWrite: RPCWriterProtocol { - func write(_ element: Element) async throws { - XCTFail("Unexpected write") - } - - func write(contentsOf elements: some Sequence) async throws { - XCTFail("Unexpected write") - } -} - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -private struct AsyncStreamGatheringWriter: RPCWriterProtocol { - let continuation: AsyncStream.Continuation - - init(continuation: AsyncStream.Continuation) { - self.continuation = continuation - } - - func write(_ element: Element) { - self.continuation.yield(element) - } - - func write(contentsOf elements: some Sequence) { - for element in elements { - self.write(element) - } - } -} diff --git a/Tests/GRPCCoreTests/Test Utilities/Services/BinaryEcho.swift b/Tests/GRPCCoreTests/Test Utilities/Services/BinaryEcho.swift deleted file mode 100644 index e5438c550..000000000 --- a/Tests/GRPCCoreTests/Test Utilities/Services/BinaryEcho.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. - */ -import GRPCCore -import XCTest - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -struct BinaryEcho: RegistrableRPCService { - func get( - _ request: ServerRequest.Single<[UInt8]> - ) async throws -> ServerResponse.Single<[UInt8]> { - ServerResponse.Single(message: request.message, metadata: request.metadata) - } - - func collect( - _ request: ServerRequest.Stream<[UInt8]> - ) async throws -> ServerResponse.Single<[UInt8]> { - let collected = try await request.messages.reduce(into: []) { $0.append(contentsOf: $1) } - return ServerResponse.Single(message: collected, metadata: request.metadata) - } - - func expand( - _ request: ServerRequest.Single<[UInt8]> - ) async throws -> ServerResponse.Stream<[UInt8]> { - return ServerResponse.Stream(metadata: request.metadata) { - for byte in request.message { - try await $0.write([byte]) - } - return [:] - } - } - - func update( - _ request: ServerRequest.Stream<[UInt8]> - ) async throws -> ServerResponse.Stream<[UInt8]> { - return ServerResponse.Stream(metadata: request.metadata) { - for try await message in request.messages { - try await $0.write(message) - } - return [:] - } - } - - func registerMethods(with router: inout RPCRouter) { - let serializer = IdentitySerializer() - let deserializer = IdentityDeserializer() - - router.registerHandler( - forMethod: Methods.get, - deserializer: deserializer, - serializer: serializer - ) { streamRequest, context in - let singleRequest = try await ServerRequest.Single(stream: streamRequest) - let singleResponse = try await self.get(singleRequest) - return ServerResponse.Stream(single: singleResponse) - } - - router.registerHandler( - forMethod: Methods.collect, - deserializer: deserializer, - serializer: serializer - ) { streamRequest, context in - let singleResponse = try await self.collect(streamRequest) - return ServerResponse.Stream(single: singleResponse) - } - - router.registerHandler( - forMethod: Methods.expand, - deserializer: deserializer, - serializer: serializer - ) { streamRequest, context in - let singleRequest = try await ServerRequest.Single(stream: streamRequest) - let streamResponse = try await self.expand(singleRequest) - return streamResponse - } - - router.registerHandler( - forMethod: Methods.update, - deserializer: deserializer, - serializer: serializer - ) { streamRequest, context in - let streamResponse = try await self.update(streamRequest) - return streamResponse - } - } - - enum Methods { - static let get = MethodDescriptor(service: "echo.Echo", method: "Get") - static let collect = MethodDescriptor(service: "echo.Echo", method: "Collect") - static let expand = MethodDescriptor(service: "echo.Echo", method: "Expand") - static let update = MethodDescriptor(service: "echo.Echo", method: "Update") - } -} diff --git a/Tests/GRPCCoreTests/Test Utilities/Transport/AnyTransport.swift b/Tests/GRPCCoreTests/Test Utilities/Transport/AnyTransport.swift deleted file mode 100644 index eb8208b72..000000000 --- a/Tests/GRPCCoreTests/Test Utilities/Transport/AnyTransport.swift +++ /dev/null @@ -1,115 +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. - */ -@testable import GRPCCore - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -struct AnyClientTransport: ClientTransport, Sendable { - typealias Inbound = RPCAsyncSequence - typealias Outbound = RPCWriter.Closable - - private let _retryThrottle: @Sendable () -> RetryThrottle? - private let _withStream: - @Sendable ( - _ method: MethodDescriptor, - _ options: CallOptions, - _ body: (RPCStream) async throws -> (any Sendable) - ) async throws -> Any - private let _connect: @Sendable () async throws -> Void - private let _close: @Sendable () -> Void - private let _configuration: @Sendable (MethodDescriptor) -> MethodConfig? - - init(wrapping transport: Transport) - where Transport.Inbound == Inbound, Transport.Outbound == Outbound { - self._retryThrottle = { transport.retryThrottle } - self._withStream = { descriptor, options, closure in - try await transport.withStream(descriptor: descriptor, options: options) { stream in - try await closure(stream) as (any Sendable) - } - } - - self._connect = { - try await transport.connect() - } - - self._close = { - transport.beginGracefulShutdown() - } - - self._configuration = { descriptor in - transport.config(forMethod: descriptor) - } - } - - var retryThrottle: RetryThrottle? { - self._retryThrottle() - } - - func connect() async throws { - try await self._connect() - } - - func beginGracefulShutdown() { - self._close() - } - - func withStream( - descriptor: MethodDescriptor, - options: CallOptions, - _ closure: (RPCStream) async throws -> T - ) async throws -> T { - let result = try await self._withStream(descriptor, options, closure) - return result as! T - } - - func config( - forMethod descriptor: MethodDescriptor - ) -> MethodConfig? { - self._configuration(descriptor) - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -struct AnyServerTransport: ServerTransport, Sendable { - typealias Inbound = RPCAsyncSequence - typealias Outbound = RPCWriter.Closable - - private let _listen: - @Sendable ( - @escaping @Sendable ( - _ stream: RPCStream, - _ context: ServerContext - ) async -> Void - ) async throws -> Void - private let _stopListening: @Sendable () -> Void - - init(wrapping transport: Transport) { - self._listen = { streamHandler in try await transport.listen(streamHandler: streamHandler) } - self._stopListening = { transport.beginGracefulShutdown() } - } - - func listen( - streamHandler: @escaping @Sendable ( - _ stream: RPCStream, - _ context: ServerContext - ) async -> Void - ) async throws { - try await self._listen(streamHandler) - } - - func beginGracefulShutdown() { - self._stopListening() - } -} diff --git a/Tests/GRPCCoreTests/Test Utilities/Transport/StreamCountingTransport.swift b/Tests/GRPCCoreTests/Test Utilities/Transport/StreamCountingTransport.swift deleted file mode 100644 index 835c81b79..000000000 --- a/Tests/GRPCCoreTests/Test Utilities/Transport/StreamCountingTransport.swift +++ /dev/null @@ -1,113 +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. - */ - -@testable import GRPCCore - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -struct StreamCountingClientTransport: ClientTransport, Sendable { - typealias Inbound = RPCAsyncSequence - typealias Outbound = RPCWriter.Closable - - private let transport: AnyClientTransport - private let _streamsOpened: AtomicCounter - private let _streamFailures: AtomicCounter - - var streamsOpened: Int { - self._streamsOpened.value - } - - var streamFailures: Int { - self._streamFailures.value - } - - init(wrapping transport: Transport) - where Transport.Inbound == Inbound, Transport.Outbound == Outbound { - self.transport = AnyClientTransport(wrapping: transport) - self._streamsOpened = AtomicCounter() - self._streamFailures = AtomicCounter() - } - - var retryThrottle: RetryThrottle? { - self.transport.retryThrottle - } - - func connect() async throws { - try await self.transport.connect() - } - - func beginGracefulShutdown() { - self.transport.beginGracefulShutdown() - } - - func withStream( - descriptor: MethodDescriptor, - options: CallOptions, - _ closure: (RPCStream) async throws -> T - ) async throws -> T { - do { - return try await self.transport.withStream( - descriptor: descriptor, - options: options - ) { stream in - self._streamsOpened.increment() - return try await closure(stream) - } - } catch { - self._streamFailures.increment() - throw error - } - } - - func config( - forMethod descriptor: MethodDescriptor - ) -> MethodConfig? { - self.transport.config(forMethod: descriptor) - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -struct StreamCountingServerTransport: ServerTransport, Sendable { - typealias Inbound = RPCAsyncSequence - typealias Outbound = RPCWriter.Closable - - private let transport: AnyServerTransport - private let _acceptedStreams: AtomicCounter - - var acceptedStreamsCount: Int { - self._acceptedStreams.value - } - - init(wrapping transport: Transport) { - self.transport = AnyServerTransport(wrapping: transport) - self._acceptedStreams = AtomicCounter() - } - - func listen( - streamHandler: @escaping @Sendable ( - _ stream: RPCStream, - _ context: ServerContext - ) async -> Void - ) async throws { - try await self.transport.listen { stream, context in - self._acceptedStreams.increment() - await streamHandler(stream, context) - } - } - - func beginGracefulShutdown() { - self.transport.beginGracefulShutdown() - } -} diff --git a/Tests/GRPCCoreTests/Test Utilities/Transport/ThrowingTransport.swift b/Tests/GRPCCoreTests/Test Utilities/Transport/ThrowingTransport.swift deleted file mode 100644 index 804be7d52..000000000 --- a/Tests/GRPCCoreTests/Test Utilities/Transport/ThrowingTransport.swift +++ /dev/null @@ -1,98 +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. - */ -@testable import GRPCCore - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -struct ThrowOnStreamCreationTransport: ClientTransport { - typealias Inbound = RPCAsyncSequence - typealias Outbound = RPCWriter.Closable - - private let code: RPCError.Code - - init(code: RPCError.Code) { - self.code = code - } - - let retryThrottle: RetryThrottle? = RetryThrottle(maxTokens: 10, tokenRatio: 0.1) - - func connect() async throws { - // no-op - } - - func beginGracefulShutdown() { - // no-op - } - - func config( - forMethod descriptor: MethodDescriptor - ) -> MethodConfig? { - return nil - } - - func withStream( - descriptor: MethodDescriptor, - options: CallOptions, - _ closure: (RPCStream) async throws -> T - ) async throws -> T { - throw RPCError(code: self.code, message: "") - } -} - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -struct ThrowOnRunServerTransport: ServerTransport { - func listen( - streamHandler: ( - _ stream: RPCStream, - _ context: ServerContext - ) async -> Void - ) async throws { - throw RPCError( - code: .unavailable, - message: "The '\(type(of: self))' transport is never available." - ) - } - - func beginGracefulShutdown() { - // no-op - } -} - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -struct ThrowOnSignalServerTransport: ServerTransport { - let signal: AsyncStream - - init(signal: AsyncStream) { - self.signal = signal - } - - func listen( - streamHandler: ( - _ stream: RPCStream, - _ context: ServerContext - ) async -> Void - ) async throws { - for await _ in self.signal {} - - throw RPCError( - code: .unavailable, - message: "The '\(type(of: self))' transport is never available." - ) - } - - func beginGracefulShutdown() { - // no-op - } -} diff --git a/Tests/GRPCCoreTests/Test Utilities/XCTest+Utilities.swift b/Tests/GRPCCoreTests/Test Utilities/XCTest+Utilities.swift deleted file mode 100644 index a6bb1ee50..000000000 --- a/Tests/GRPCCoreTests/Test Utilities/XCTest+Utilities.swift +++ /dev/null @@ -1,183 +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. - */ -import GRPCCore -import XCTest - -func XCTAssertDescription( - _ subject: some CustomStringConvertible, - _ expected: String, - file: StaticString = #filePath, - line: UInt = #line -) { - XCTAssertEqual(String(describing: subject), expected, file: file, line: line) -} - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -func XCTAssertThrowsErrorAsync( - _ expression: () async throws -> T, - errorHandler: (any Error) -> Void -) async { - do { - _ = try await expression() - XCTFail("Expression didn't throw") - } catch { - errorHandler(error) - } -} - -func XCTAssertThrowsError( - ofType: E.Type, - _ expression: @autoclosure () throws -> T, - _ errorHandler: (E) -> Void -) { - XCTAssertThrowsError(try expression()) { error in - guard let error = error as? E else { - return XCTFail("Error had unexpected type '\(type(of: error))'") - } - errorHandler(error) - } -} - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -func XCTAssertThrowsErrorAsync( - ofType: E.Type = E.self, - _ expression: () async throws -> T, - errorHandler: (E) -> Void -) async { - do { - _ = try await expression() - XCTFail("Expression didn't throw") - } catch let error as E { - errorHandler(error) - } catch { - XCTFail("Error had unexpected type '\(type(of: error))'") - } -} - -func XCTAssertThrowsRPCError( - _ expression: @autoclosure () throws -> T, - _ errorHandler: (RPCError) -> Void -) { - XCTAssertThrowsError(try expression()) { error in - guard let error = error as? RPCError else { - return XCTFail("Error had unexpected type '\(type(of: error))'") - } - - errorHandler(error) - } -} - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -func XCTAssertThrowsRPCErrorAsync( - _ expression: () async throws -> T, - errorHandler: (RPCError) -> Void -) async { - do { - _ = try await expression() - XCTFail("Expression didn't throw") - } catch let error as RPCError { - errorHandler(error) - } catch { - XCTFail("Error had unexpected type '\(type(of: error))'") - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -func XCTAssertRejected( - _ response: ClientResponse.Stream, - errorHandler: (RPCError) -> Void -) { - switch response.accepted { - case .success: - XCTFail("Expected RPC to be rejected") - case .failure(let error): - errorHandler(error) - } -} - -func XCTAssertRejected( - _ response: ClientResponse.Single, - errorHandler: (RPCError) -> Void -) { - switch response.accepted { - case .success: - XCTFail("Expected RPC to be rejected") - case .failure(let error): - errorHandler(error) - } -} - -func XCTAssertMetadata( - _ part: RPCResponsePart?, - metadataHandler: (Metadata) -> Void = { _ in } -) { - switch part { - case .some(.metadata(let metadata)): - metadataHandler(metadata) - default: - XCTFail("Expected '.metadata' but found '\(String(describing: part))'") - } -} - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -func XCTAssertMetadata( - _ part: RPCRequestPart?, - metadataHandler: (Metadata) async throws -> Void = { _ in } -) async throws { - switch part { - case .some(.metadata(let metadata)): - try await metadataHandler(metadata) - default: - XCTFail("Expected '.metadata' but found '\(String(describing: part))'") - } -} - -func XCTAssertMessage( - _ part: RPCResponsePart?, - messageHandler: ([UInt8]) -> Void = { _ in } -) { - switch part { - case .some(.message(let message)): - messageHandler(message) - default: - XCTFail("Expected '.message' but found '\(String(describing: part))'") - } -} - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -func XCTAssertMessage( - _ part: RPCRequestPart?, - messageHandler: ([UInt8]) async throws -> Void = { _ in } -) async throws { - switch part { - case .some(.message(let message)): - try await messageHandler(message) - default: - XCTFail("Expected '.message' but found '\(String(describing: part))'") - } -} - -func XCTAssertStatus( - _ part: RPCResponsePart?, - statusHandler: (Status, Metadata) -> Void = { _, _ in } -) { - switch part { - case .some(.status(let status, let metadata)): - statusHandler(status, metadata) - default: - XCTFail("Expected '.status' but found '\(String(describing: part))'") - } -} diff --git a/Tests/GRPCCoreTests/TimeoutTests.swift b/Tests/GRPCCoreTests/TimeoutTests.swift deleted file mode 100644 index a22bb32be..000000000 --- a/Tests/GRPCCoreTests/TimeoutTests.swift +++ /dev/null @@ -1,84 +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. - */ - -import Testing - -@testable import GRPCCore - -struct TimeoutTests { - @Test("Initialize from invalid String value", arguments: ["", "H", "123", "100000000S", "123j"]) - func initFromStringWithInvalidValue(_ value: String) throws { - #expect(Timeout(decoding: value) == nil) - } - - @Test( - "Initialize from String", - arguments: [ - ("123H", .hours(123)), - ("123M", .minutes(123)), - ("123S", .seconds(123)), - ("123m", .milliseconds(123)), - ("123u", .microseconds(123)), - ("123n", .nanoseconds(123)), - ] as [(String, Duration)] - ) - func initFromString(_ value: String, expected: Duration) throws { - let timeout = try #require(Timeout(decoding: value)) - #expect(timeout.duration == expected) - } - - @Test( - "Initialize from Duration", - arguments: [ - .hours(123), - .minutes(43), - .seconds(12345), - .milliseconds(100), - .microseconds(100), - .nanoseconds(100), - ] as [Duration] - ) - func initFromDuration(_ value: Duration) { - let timeout = Timeout(duration: value) - #expect(timeout.duration == value) - } - - @Test( - "Initialize from Duration with loss of precision", - arguments: [ - // 111,111,111 seconds / 60 = 1,851,851.85 minutes -rounding up-> 1,851,852 minutes * 60 = 111,111,120 seconds - (.seconds(111_111_111), .minutes(1_851_852)), - - // 9,999,999,999 seconds / 60 = 166,666,666.65 minutes -rounding up-> - // 166,666,667 minutes / 60 = 2,777,777.78 hours -rounding up-> - // 2,777,778 hours * 60 -> 166,666,680 minutes * 60 = 10,000,000,800 seconds - (.seconds(9_999_999_999 as Int64), .hours(2_777_778)), - - // The conversion from seconds to hours results in a number that still has - // more than the maximum allowed 8 digits, so we must clamp it. - // Make sure that `Timeout.maxAmount` is the amount used for the resulting timeout. - (.seconds(999_999_999_999 as Int64), .hours(Timeout.maxAmount)), - - // We can't convert seconds to nanoseconds because that would require at least - // 9 digits, and the maximum allowed is 8: we expect to simply drop the nanoseconds. - (Duration(secondsComponent: 1, attosecondsComponent: Int64(1e11)), .seconds(1)), - ] as [(Duration, Duration)] - ) - func initFromDurationWithLossOfPrecision(original: Duration, rounded: Duration) { - let timeout = Timeout(duration: original) - #expect(timeout.duration == rounded) - } -} diff --git a/Tests/GRPCCoreTests/Transport/RetryThrottleTests.swift b/Tests/GRPCCoreTests/Transport/RetryThrottleTests.swift deleted file mode 100644 index ee2ad3742..000000000 --- a/Tests/GRPCCoreTests/Transport/RetryThrottleTests.swift +++ /dev/null @@ -1,99 +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. - */ - -import XCTest - -@testable import GRPCCore - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -final class RetryThrottleTests: XCTestCase { - func testThrottleOnInit() { - let throttle = RetryThrottle(maxTokens: 10, tokenRatio: 0.1) - // Start with max tokens, so permitted. - XCTAssertTrue(throttle.isRetryPermitted) - XCTAssertEqual(throttle.maxTokens, 10) - XCTAssertEqual(throttle.tokens, 10) - XCTAssertEqual(throttle.tokenRatio, 0.1) - } - - func testThrottleIgnoresMoreThanThreeDecimals() { - let throttle = RetryThrottle(maxTokens: 10, tokenRatio: 0.1239) - XCTAssertEqual(throttle.tokenRatio, 0.123) - } - - func testFailureReducesTokens() { - let throttle = RetryThrottle(maxTokens: 10, tokenRatio: 0.1) - XCTAssertEqual(throttle.tokens, 10) - XCTAssert(throttle.isRetryPermitted) - - throttle.recordFailure() - XCTAssertEqual(throttle.tokens, 9) - XCTAssert(throttle.isRetryPermitted) - - throttle.recordFailure() - XCTAssertEqual(throttle.tokens, 8) - XCTAssert(throttle.isRetryPermitted) - - throttle.recordFailure() - XCTAssertEqual(throttle.tokens, 7) - XCTAssert(throttle.isRetryPermitted) - - throttle.recordFailure() - XCTAssertEqual(throttle.tokens, 6) - XCTAssert(throttle.isRetryPermitted) - - // Drop to threshold, retries no longer allowed. - throttle.recordFailure() - XCTAssertEqual(throttle.tokens, 5) - XCTAssertFalse(throttle.isRetryPermitted) - } - - func testTokensCantDropBelowZero() { - let throttle = RetryThrottle(maxTokens: 10, tokenRatio: 0.1) - for _ in 0 ..< 1000 { - throttle.recordFailure() - XCTAssertGreaterThanOrEqual(throttle.tokens, 0) - } - XCTAssertEqual(throttle.tokens, 0) - } - - func testSuccessIncreasesTokens() { - let throttle = RetryThrottle(maxTokens: 10, tokenRatio: 0.1) - - // Drop to zero. - for _ in 0 ..< 10 { - throttle.recordFailure() - } - XCTAssertEqual(throttle.tokens, 0) - - // Start recording successes. - throttle.recordSuccess() - XCTAssertEqual(throttle.tokens, 0.1) - - throttle.recordSuccess() - XCTAssertEqual(throttle.tokens, 0.2) - - throttle.recordSuccess() - XCTAssertEqual(throttle.tokens, 0.3) - } - - func testTokensCantRiseAboveMax() { - let throttle = RetryThrottle(maxTokens: 10, tokenRatio: 0.1) - XCTAssertEqual(throttle.tokens, 10) - throttle.recordSuccess() - XCTAssertEqual(throttle.tokens, 10) - } -} diff --git a/Tests/GRPCHTTP2CoreTests/Client/Connection/ClientConnectionHandlerStateMachineTests.swift b/Tests/GRPCHTTP2CoreTests/Client/Connection/ClientConnectionHandlerStateMachineTests.swift deleted file mode 100644 index fdf14aa2d..000000000 --- a/Tests/GRPCHTTP2CoreTests/Client/Connection/ClientConnectionHandlerStateMachineTests.swift +++ /dev/null @@ -1,115 +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 NIOCore -import NIOEmbedded -import XCTest - -@testable import GRPCHTTP2Core - -final class ClientConnectionHandlerStateMachineTests: XCTestCase { - private func makeStateMachine( - keepaliveWithoutCalls: Bool = false - ) -> ClientConnectionHandler.StateMachine { - return ClientConnectionHandler.StateMachine(allowKeepaliveWithoutCalls: keepaliveWithoutCalls) - } - - func testCloseSomeStreamsWhenActive() { - var state = self.makeStateMachine() - state.streamOpened(1) - state.streamOpened(2) - XCTAssertEqual(state.streamClosed(2), .none) - XCTAssertEqual(state.streamClosed(1), .startIdleTimer(cancelKeepalive: true)) - } - - func testCloseSomeStreamsWhenClosing() { - var state = self.makeStateMachine() - state.streamOpened(1) - state.streamOpened(2) - XCTAssertTrue(state.beginClosing()) - XCTAssertEqual(state.streamClosed(2), .none) - XCTAssertEqual(state.streamClosed(1), .close) - } - - func testCloseWhenAlreadyClosingGracefully() { - var state = self.makeStateMachine() - state.streamOpened(1) - XCTAssertEqual(state.beginGracefulShutdown(promise: nil), .sendGoAway(false)) - XCTAssertTrue(state.beginClosing()) - } - - func testOpenAndCloseStreamWhenClosed() { - var state = self.makeStateMachine() - _ = state.closed() - state.streamOpened(1) - XCTAssertEqual(state.streamClosed(1), .none) - } - - func testSendKeepalivePing() { - var state = self.makeStateMachine(keepaliveWithoutCalls: false) - // No streams open so ping isn't allowed. - XCTAssertFalse(state.sendKeepalivePing()) - - // Stream open, ping allowed. - state.streamOpened(1) - XCTAssertTrue(state.sendKeepalivePing()) - - // No stream, no ping. - XCTAssertEqual(state.streamClosed(1), .startIdleTimer(cancelKeepalive: true)) - XCTAssertFalse(state.sendKeepalivePing()) - } - - func testSendKeepalivePingWhenAllowedWithoutCalls() { - var state = self.makeStateMachine(keepaliveWithoutCalls: true) - // Keep alive is allowed when no streams are open, so pings are allowed. - XCTAssertTrue(state.sendKeepalivePing()) - - state.streamOpened(1) - XCTAssertTrue(state.sendKeepalivePing()) - - XCTAssertEqual(state.streamClosed(1), .startIdleTimer(cancelKeepalive: false)) - XCTAssertTrue(state.sendKeepalivePing()) - } - - func testSendKeepalivePingWhenClosing() { - var state = self.makeStateMachine(keepaliveWithoutCalls: false) - state.streamOpened(1) - XCTAssertTrue(state.beginClosing()) - - // Stream is opened and state is closing, ping is allowed. - XCTAssertTrue(state.sendKeepalivePing()) - } - - func testSendKeepalivePingWhenClosed() { - var state = self.makeStateMachine(keepaliveWithoutCalls: true) - _ = state.closed() - XCTAssertFalse(state.sendKeepalivePing()) - } - - func testBeginGracefulShutdownWhenStreamsAreOpen() { - var state = self.makeStateMachine() - state.streamOpened(1) - // Close is false as streams are still open. - XCTAssertEqual(state.beginGracefulShutdown(promise: nil), .sendGoAway(false)) - } - - func testBeginGracefulShutdownWhenNoStreamsAreOpen() { - var state = self.makeStateMachine() - // Close immediately, not streams are open. - XCTAssertEqual(state.beginGracefulShutdown(promise: nil), .sendGoAway(true)) - } - -} diff --git a/Tests/GRPCHTTP2CoreTests/Client/Connection/ClientConnectionHandlerTests.swift b/Tests/GRPCHTTP2CoreTests/Client/Connection/ClientConnectionHandlerTests.swift deleted file mode 100644 index 87ac5538c..000000000 --- a/Tests/GRPCHTTP2CoreTests/Client/Connection/ClientConnectionHandlerTests.swift +++ /dev/null @@ -1,405 +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 NIOCore -import NIOEmbedded -import NIOHTTP2 -import XCTest - -final class ClientConnectionHandlerTests: XCTestCase { - func testMaxIdleTime() throws { - let connection = try Connection(maxIdleTime: .minutes(5)) - try connection.activate() - - // Write the initial settings to ready the connection. - try connection.settings([]) - XCTAssertEqual(try connection.readEvent(), .ready) - - // Idle with no streams open we should: - // - read out a closing event, - // - write a GOAWAY frame, - // - close. - connection.loop.advanceTime(by: .minutes(5)) - - XCTAssertEqual(try connection.readEvent(), .closing(.idle)) - - let frame = try XCTUnwrap(try connection.readFrame()) - XCTAssertEqual(frame.streamID, .rootStream) - XCTAssertGoAway(frame.payload) { lastStreamID, error, data in - XCTAssertEqual(lastStreamID, .rootStream) - XCTAssertEqual(error, .noError) - XCTAssertEqual(data, ByteBuffer(string: "idle")) - } - - try connection.waitUntilClosed() - } - - func testMaxIdleTimeWhenOpenStreams() throws { - let connection = try Connection(maxIdleTime: .minutes(5)) - try connection.activate() - - // Open a stream, the idle timer should be cancelled. - connection.streamOpened(1) - - // Advance by the idle time, nothing should happen. - connection.loop.advanceTime(by: .minutes(5)) - XCTAssertNil(try connection.readEvent()) - XCTAssertNil(try connection.readFrame()) - - // Close the stream, the idle timer should begin again. - connection.streamClosed(1) - connection.loop.advanceTime(by: .minutes(5)) - let frame = try XCTUnwrap(try connection.readFrame()) - XCTAssertGoAway(frame.payload) { lastStreamID, error, data in - XCTAssertEqual(lastStreamID, .rootStream) - XCTAssertEqual(error, .noError) - XCTAssertEqual(data, ByteBuffer(string: "idle")) - } - - try connection.waitUntilClosed() - } - - func testKeepaliveWithOpenStreams() throws { - let connection = try Connection(keepaliveTime: .minutes(1), keepaliveTimeout: .seconds(10)) - try connection.activate() - - // Write the initial settings to ready the connection. - try connection.settings([]) - XCTAssertEqual(try connection.readEvent(), .ready) - - // Open a stream so keep-alive starts. - connection.streamOpened(1) - - for _ in 0 ..< 10 { - // Advance time, a PING should be sent, ACK it. - connection.loop.advanceTime(by: .minutes(1)) - let frame1 = try XCTUnwrap(connection.readFrame()) - XCTAssertEqual(frame1.streamID, .rootStream) - try XCTAssertPing(frame1.payload) { data, ack in - XCTAssertFalse(ack) - try connection.ping(data: data, ack: true) - } - - XCTAssertNil(try connection.readFrame()) - } - - // Close the stream, keep-alive pings should stop. - connection.streamClosed(1) - connection.loop.advanceTime(by: .minutes(1)) - XCTAssertNil(try connection.readFrame()) - } - - func testKeepaliveWithNoOpenStreams() throws { - let connection = try Connection(keepaliveTime: .minutes(1), allowKeepaliveWithoutCalls: true) - try connection.activate() - - // Write the initial settings to ready the connection. - try connection.settings([]) - XCTAssertEqual(try connection.readEvent(), .ready) - - for _ in 0 ..< 10 { - // Advance time, a PING should be sent, ACK it. - connection.loop.advanceTime(by: .minutes(1)) - let frame1 = try XCTUnwrap(connection.readFrame()) - XCTAssertEqual(frame1.streamID, .rootStream) - try XCTAssertPing(frame1.payload) { data, ack in - XCTAssertFalse(ack) - try connection.ping(data: data, ack: true) - } - - XCTAssertNil(try connection.readFrame()) - } - } - - func testKeepaliveWithOpenStreamsTimingOut() throws { - let connection = try Connection(keepaliveTime: .minutes(1), keepaliveTimeout: .seconds(10)) - try connection.activate() - - // Write the initial settings to ready the connection. - try connection.settings([]) - XCTAssertEqual(try connection.readEvent(), .ready) - - // Open a stream so keep-alive starts. - connection.streamOpened(1) - - // Advance time, a PING should be sent, don't ACK it. - connection.loop.advanceTime(by: .minutes(1)) - let frame1 = try XCTUnwrap(connection.readFrame()) - XCTAssertEqual(frame1.streamID, .rootStream) - XCTAssertPing(frame1.payload) { _, ack in - XCTAssertFalse(ack) - } - - // Advance time by the keep alive timeout. We should: - // - read a connection event - // - read out a GOAWAY frame - // - be closed - connection.loop.advanceTime(by: .seconds(10)) - - XCTAssertEqual(try connection.readEvent(), .closing(.keepaliveExpired)) - - let frame2 = try XCTUnwrap(connection.readFrame()) - XCTAssertEqual(frame2.streamID, .rootStream) - XCTAssertGoAway(frame2.payload) { lastStreamID, error, data in - XCTAssertEqual(lastStreamID, .rootStream) - XCTAssertEqual(error, .noError) - XCTAssertEqual(data, ByteBuffer(string: "keepalive_expired")) - } - - // Doesn't wait for streams to close: the connection is bad. - try connection.waitUntilClosed() - } - - func testPingsAreIgnored() throws { - let connection = try Connection() - try connection.activate() - - // PING frames without ack set should be ignored, we rely on the HTTP/2 handler replying to them. - try connection.ping(data: HTTP2PingData(), ack: false) - XCTAssertNil(try connection.readFrame()) - } - - func testReceiveGoAway() throws { - let connection = try Connection() - try connection.activate() - - try connection.goAway( - lastStreamID: 0, - errorCode: .enhanceYourCalm, - opaqueData: ByteBuffer(string: "too_many_pings") - ) - - // Should read out an event and close (because there are no open streams). - XCTAssertEqual( - try connection.readEvent(), - .closing(.goAway(.enhanceYourCalm, "too_many_pings")) - ) - try connection.waitUntilClosed() - } - - func testReceiveGoAwayWithOpenStreams() throws { - let connection = try Connection() - try connection.activate() - - connection.streamOpened(1) - connection.streamOpened(2) - connection.streamOpened(3) - - try connection.goAway(lastStreamID: .maxID, errorCode: .noError) - - // Should read out an event. - XCTAssertEqual(try connection.readEvent(), .closing(.goAway(.noError, ""))) - - // Close streams so the connection can close. - connection.streamClosed(1) - connection.streamClosed(2) - connection.streamClosed(3) - try connection.waitUntilClosed() - } - - func testGoAwayWithNoErrorThenGoAwayWithProtocolError() throws { - let connection = try Connection() - try connection.activate() - - connection.streamOpened(1) - connection.streamOpened(2) - connection.streamOpened(3) - - try connection.goAway(lastStreamID: .maxID, errorCode: .noError) - // Should read out an event. - XCTAssertEqual(try connection.readEvent(), .closing(.goAway(.noError, ""))) - - // Upgrade the close from graceful to 'error'. - try connection.goAway(lastStreamID: .maxID, errorCode: .protocolError) - // Should read out an event and the connection will be closed without waiting for notification - // from existing streams. - XCTAssertEqual(try connection.readEvent(), .closing(.goAway(.protocolError, ""))) - try connection.waitUntilClosed() - } - - func testOutboundGracefulClose() throws { - let connection = try Connection() - try connection.activate() - - connection.streamOpened(1) - let closed = connection.closeGracefully() - XCTAssertEqual(try connection.readEvent(), .closing(.initiatedLocally)) - connection.streamClosed(1) - try closed.wait() - } - - func testReceiveInitialSettings() throws { - let connection = try Connection() - try connection.activate() - - // Nothing yet. - XCTAssertNil(try connection.readEvent()) - - // Write the initial settings. - try connection.settings([]) - XCTAssertEqual(try connection.readEvent(), .ready) - - // Receiving another settings frame should be a no-op. - try connection.settings([]) - XCTAssertNil(try connection.readEvent()) - } - - func testReceiveErrorWhenIdle() throws { - let connection = try Connection() - try connection.activate() - - // Write the initial settings. - try connection.settings([]) - XCTAssertEqual(try connection.readEvent(), .ready) - - // Write an error and close. - let error = RPCError(code: .aborted, message: "") - connection.channel.pipeline.fireErrorCaught(error) - connection.channel.close(mode: .all, promise: nil) - - XCTAssertEqual(try connection.readEvent(), .closing(.unexpected(error, isIdle: true))) - } - - func testReceiveErrorWhenStreamsAreOpen() throws { - let connection = try Connection() - try connection.activate() - - // Write the initial settings. - try connection.settings([]) - XCTAssertEqual(try connection.readEvent(), .ready) - - // Open a stream. - connection.streamOpened(1) - - // Write an error and close. - let error = RPCError(code: .aborted, message: "") - connection.channel.pipeline.fireErrorCaught(error) - connection.channel.close(mode: .all, promise: nil) - - XCTAssertEqual(try connection.readEvent(), .closing(.unexpected(error, isIdle: false))) - } - - func testUnexpectedCloseWhenIdle() throws { - let connection = try Connection() - try connection.activate() - - // Write the initial settings. - try connection.settings([]) - XCTAssertEqual(try connection.readEvent(), .ready) - - connection.channel.close(mode: .all, promise: nil) - XCTAssertEqual(try connection.readEvent(), .closing(.unexpected(nil, isIdle: true))) - } - - func testUnexpectedCloseWhenStreamsAreOpen() throws { - let connection = try Connection() - try connection.activate() - - // Write the initial settings. - try connection.settings([]) - XCTAssertEqual(try connection.readEvent(), .ready) - - connection.streamOpened(1) - connection.channel.close(mode: .all, promise: nil) - XCTAssertEqual(try connection.readEvent(), .closing(.unexpected(nil, isIdle: false))) - } -} - -extension ClientConnectionHandlerTests { - struct Connection { - let channel: EmbeddedChannel - let streamDelegate: any NIOHTTP2StreamDelegate - var loop: EmbeddedEventLoop { - self.channel.embeddedEventLoop - } - - init( - maxIdleTime: TimeAmount? = nil, - keepaliveTime: TimeAmount? = nil, - keepaliveTimeout: TimeAmount? = nil, - allowKeepaliveWithoutCalls: Bool = false - ) throws { - let loop = EmbeddedEventLoop() - let handler = ClientConnectionHandler( - eventLoop: loop, - maxIdleTime: maxIdleTime, - keepaliveTime: keepaliveTime, - keepaliveTimeout: keepaliveTimeout, - keepaliveWithoutCalls: allowKeepaliveWithoutCalls - ) - - self.streamDelegate = handler.http2StreamDelegate - self.channel = EmbeddedChannel(handler: handler, loop: loop) - } - - func activate() throws { - try self.channel.connect(to: SocketAddress(ipAddress: "127.0.0.1", port: 0)).wait() - } - - func streamOpened(_ id: HTTP2StreamID) { - self.streamDelegate.streamCreated(id, channel: self.channel) - } - - func streamClosed(_ id: HTTP2StreamID) { - self.streamDelegate.streamClosed(id, channel: self.channel) - } - - func goAway( - lastStreamID: HTTP2StreamID, - errorCode: HTTP2ErrorCode, - opaqueData: ByteBuffer? = nil - ) throws { - let frame = HTTP2Frame( - streamID: .rootStream, - payload: .goAway(lastStreamID: lastStreamID, errorCode: errorCode, opaqueData: opaqueData) - ) - - try self.channel.writeInbound(frame) - } - - func ping(data: HTTP2PingData, ack: Bool) throws { - let frame = HTTP2Frame(streamID: .rootStream, payload: .ping(data, ack: ack)) - try self.channel.writeInbound(frame) - } - - func settings(_ settings: [HTTP2Setting]) throws { - let frame = HTTP2Frame(streamID: .rootStream, payload: .settings(.settings(settings))) - try self.channel.writeInbound(frame) - } - - func readFrame() throws -> HTTP2Frame? { - return try self.channel.readOutbound(as: HTTP2Frame.self) - } - - func readEvent() throws -> ClientConnectionEvent? { - return try self.channel.readInbound(as: ClientConnectionEvent.self) - } - - func waitUntilClosed() throws { - self.channel.embeddedEventLoop.run() - try self.channel.closeFuture.wait() - } - - func closeGracefully() -> EventLoopFuture { - let promise = self.channel.embeddedEventLoop.makePromise(of: Void.self) - let event = ClientConnectionHandler.OutboundEvent.closeGracefully - self.channel.pipeline.triggerUserOutboundEvent(event, promise: promise) - return promise.futureResult - } - } -} diff --git a/Tests/GRPCHTTP2CoreTests/Client/Connection/Connection+Equatable.swift b/Tests/GRPCHTTP2CoreTests/Client/Connection/Connection+Equatable.swift deleted file mode 100644 index 171e886ac..000000000 --- a/Tests/GRPCHTTP2CoreTests/Client/Connection/Connection+Equatable.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. - */ - -import GRPCCore -import GRPCHTTP2Core - -// Equatable conformance for these types is 'best effort', this is sufficient for testing but not -// for general use. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension Connection.Event: Equatable {} -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension Connection.CloseReason: Equatable {} - -extension ClientConnectionEvent: Equatable {} -extension ClientConnectionEvent.CloseReason: Equatable {} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension Connection.Event { - package static func == (lhs: Connection.Event, rhs: Connection.Event) -> Bool { - switch (lhs, rhs) { - case (.connectSucceeded, .connectSucceeded), - (.connectFailed, .connectFailed): - return true - - case (.goingAway(let lhsCode, let lhsReason), .goingAway(let rhsCode, let rhsReason)): - return lhsCode == rhsCode && lhsReason == rhsReason - - case (.closed(let lhsReason), .closed(let rhsReason)): - return lhsReason == rhsReason - - default: - return false - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension Connection.CloseReason { - package static func == (lhs: Connection.CloseReason, rhs: Connection.CloseReason) -> Bool { - switch (lhs, rhs) { - case (.idleTimeout, .idleTimeout), - (.keepaliveTimeout, .keepaliveTimeout), - (.initiatedLocally, .initiatedLocally), - (.remote, .remote): - return true - - case (.error(let lhsError, let lhsStreams), .error(let rhsError, let rhsStreams)): - if let lhs = lhsError as? RPCError, let rhs = rhsError as? RPCError { - return lhs == rhs && lhsStreams == rhsStreams - } else { - return lhsStreams == rhsStreams - } - - default: - return false - } - } -} - -extension ClientConnectionEvent { - package static func == (lhs: ClientConnectionEvent, rhs: ClientConnectionEvent) -> Bool { - switch (lhs, rhs) { - case (.ready, .ready): - return true - case (.closing(let lhsReason), .closing(let rhsReason)): - return lhsReason == rhsReason - default: - return false - } - } -} - -extension ClientConnectionEvent.CloseReason { - package static func == (lhs: Self, rhs: Self) -> Bool { - switch (lhs, rhs) { - case (.goAway(let lhsCode, let lhsMessage), .goAway(let rhsCode, let rhsMessage)): - return lhsCode == rhsCode && lhsMessage == rhsMessage - case (.unexpected(let lhsError, let lhsIsIdle), .unexpected(let rhsError, let rhsIsIdle)): - if let lhs = lhsError as? RPCError, let rhs = rhsError as? RPCError { - return lhs == rhs && lhsIsIdle == rhsIsIdle - } else { - return lhsIsIdle == rhsIsIdle - } - case (.keepaliveExpired, .keepaliveExpired), - (.idle, .idle), - (.initiatedLocally, .initiatedLocally): - return true - default: - return false - } - } -} diff --git a/Tests/GRPCHTTP2CoreTests/Client/Connection/ConnectionBackoffTests.swift b/Tests/GRPCHTTP2CoreTests/Client/Connection/ConnectionBackoffTests.swift deleted file mode 100644 index 3898513ca..000000000 --- a/Tests/GRPCHTTP2CoreTests/Client/Connection/ConnectionBackoffTests.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 XCTest - -@testable import GRPCHTTP2Core - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -final class ConnectionBackoffTests: XCTestCase { - func testUnjitteredBackoff() { - let backoff = ConnectionBackoff( - initial: .seconds(10), - max: .seconds(30), - multiplier: 1.5, - jitter: 0.0 - ) - - var iterator = backoff.makeIterator() - XCTAssertEqual(iterator.next(), .seconds(10)) - // 10 * 1.5 = 15 seconds - XCTAssertEqual(iterator.next(), .seconds(15)) - // 15 * 1.5 = 22.5 seconds - XCTAssertEqual(iterator.next(), .seconds(22.5)) - // 22.5 * 1.5 = 33.75 seconds, clamped to 30 seconds, all future values will be the same. - XCTAssertEqual(iterator.next(), .seconds(30)) - XCTAssertEqual(iterator.next(), .seconds(30)) - XCTAssertEqual(iterator.next(), .seconds(30)) - } - - func testJitteredBackoff() { - let backoff = ConnectionBackoff( - initial: .seconds(10), - max: .seconds(30), - multiplier: 1.5, - jitter: 0.1 - ) - - var iterator = backoff.makeIterator() - - // Initial isn't jittered. - XCTAssertEqual(iterator.next(), .seconds(10)) - - // Next value should be 10 * 1.5 = 15 seconds ± 1.5 seconds - var expected: ClosedRange = .seconds(13.5) ... .seconds(16.5) - XCTAssert(expected.contains(iterator.next())) - - // Next value should be 15 * 1.5 = 22.5 seconds ± 2.25 seconds - expected = .seconds(20.25) ... .seconds(24.75) - XCTAssert(expected.contains(iterator.next())) - - // Next value should be 22.5 * 1.5 = 33.75 seconds, clamped to 30 seconds ± 3 seconds. - // All future values will be in the same range. - expected = .seconds(27) ... .seconds(33) - XCTAssert(expected.contains(iterator.next())) - XCTAssert(expected.contains(iterator.next())) - XCTAssert(expected.contains(iterator.next())) - } -} diff --git a/Tests/GRPCHTTP2CoreTests/Client/Connection/ConnectionTests.swift b/Tests/GRPCHTTP2CoreTests/Client/Connection/ConnectionTests.swift deleted file mode 100644 index bcc6d86ed..000000000 --- a/Tests/GRPCHTTP2CoreTests/Client/Connection/ConnectionTests.swift +++ /dev/null @@ -1,255 +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 DequeModule -import GRPCCore -import GRPCHTTP2Core -import NIOCore -import NIOHPACK -import NIOHTTP2 -import NIOPosix -import XCTest - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -final class ConnectionTests: XCTestCase { - func testConnectThenClose() async throws { - try await ConnectionTest.run(connector: .posix()) { context, event in - switch event { - case .connectSucceeded: - context.connection.close() - default: - () - } - } validateEvents: { _, events in - XCTAssertEqual(events, [.connectSucceeded, .closed(.initiatedLocally)]) - } - } - - func testConnectThenIdleTimeout() async throws { - try await ConnectionTest.run(connector: .posix(maxIdleTime: .milliseconds(50))) { _, events in - XCTAssertEqual(events, [.connectSucceeded, .closed(.idleTimeout)]) - } - } - - func testConnectThenKeepaliveTimeout() async throws { - try await ConnectionTest.run( - connector: .posix( - keepaliveTime: .milliseconds(50), - keepaliveTimeout: .milliseconds(10), - keepaliveWithoutCalls: true, - dropPingAcks: true - ) - ) { _, events in - XCTAssertEqual(events, [.connectSucceeded, .closed(.keepaliveTimeout)]) - } - } - - func testGoAwayWhenConnected() async throws { - try await ConnectionTest.run(connector: .posix()) { context, event in - switch event { - case .connectSucceeded: - let goAway = HTTP2Frame( - streamID: .rootStream, - payload: .goAway( - lastStreamID: 0, - errorCode: .noError, - opaqueData: ByteBuffer(string: "Hello!") - ) - ) - - let accepted = try context.server.acceptedChannel - accepted.writeAndFlush(goAway, promise: nil) - - default: - () - } - } validateEvents: { _, events in - XCTAssertEqual(events, [.connectSucceeded, .goingAway(.noError, "Hello!"), .closed(.remote)]) - } - } - - func testConnectionDropWhenConnected() async throws { - try await ConnectionTest.run(connector: .posix()) { context, event in - switch event { - case .connectSucceeded: - let accepted = try context.server.acceptedChannel - accepted.close(mode: .all, promise: nil) - - default: - () - } - } validateEvents: { _, events in - let error = RPCError( - code: .unavailable, - message: "The TCP connection was dropped unexpectedly." - ) - - let expected: [Connection.Event] = [.connectSucceeded, .closed(.error(error, wasIdle: true))] - XCTAssertEqual(events, expected) - } - } - - func testConnectFails() async throws { - let error = RPCError(code: .unimplemented, message: "") - try await ConnectionTest.run(connector: .throwing(error)) { _, events in - XCTAssertEqual(events, [.connectFailed(error)]) - } - } - - func testConnectFailsOnAcceptedThenClosedTCPConnection() async throws { - try await ConnectionTest.run(connector: .posix(), server: .closeOnAccept) { _, events in - XCTAssertEqual(events.count, 1) - let event = try XCTUnwrap(events.first) - switch event { - case .connectFailed(let error): - XCTAssert(error, as: RPCError.self) { rpcError in - XCTAssertEqual(rpcError.code, .unavailable) - } - default: - XCTFail("Expected '.connectFailed', got '\(event)'") - } - } - } - - func testMakeStreamOnActiveConnection() async throws { - try await ConnectionTest.run(connector: .posix()) { context, event in - switch event { - case .connectSucceeded: - let stream = try await context.connection.makeStream( - descriptor: .echoGet, - options: .defaults - ) - try await stream.execute { inbound, outbound in - try await outbound.write(.metadata(["foo": "bar", "bar": "baz"])) - try await outbound.write(.message([0, 1, 2])) - outbound.finish() - - var parts = [RPCResponsePart]() - for try await part in inbound { - switch part { - case .metadata(let metadata): - // Filter out any transport specific metadata - parts.append(.metadata(Metadata(metadata.suffix(2)))) - case .message, .status: - parts.append(part) - } - } - - let expected: [RPCResponsePart] = [ - .metadata(["foo": "bar", "bar": "baz"]), - .message([0, 1, 2]), - .status(Status(code: .ok, message: ""), [:]), - ] - XCTAssertEqual(parts, expected) - } - - context.connection.close() - - default: - () - } - } validateEvents: { _, events in - XCTAssertEqual(events, [.connectSucceeded, .closed(.initiatedLocally)]) - } - } - - func testMakeStreamOnClosedConnection() async throws { - try await ConnectionTest.run(connector: .posix()) { context, event in - switch event { - case .connectSucceeded: - context.connection.close() - case .closed: - await XCTAssertThrowsErrorAsync(ofType: RPCError.self) { - _ = try await context.connection.makeStream(descriptor: .echoGet, options: .defaults) - } errorHandler: { error in - XCTAssertEqual(error.code, .unavailable) - } - default: - () - } - } validateEvents: { context, events in - XCTAssertEqual(events, [.connectSucceeded, .closed(.initiatedLocally)]) - } - } - - func testMakeStreamOnNotRunningConnection() async throws { - let connection = Connection( - address: .ipv4(host: "ignored", port: 0), - http2Connector: .never, - defaultCompression: .none, - enabledCompression: .none - ) - - await XCTAssertThrowsErrorAsync(ofType: RPCError.self) { - _ = try await connection.makeStream(descriptor: .echoGet, options: .defaults) - } errorHandler: { error in - XCTAssertEqual(error.code, .unavailable) - } - } -} - -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 -> T { - if let ipv4 = address.ipv4 { - return try await self.connect( - host: ipv4.host, - port: ipv4.port, - channelInitializer: configure - ) - } else if let ipv6 = address.ipv6 { - return try await self.connect( - host: ipv6.host, - port: ipv6.port, - channelInitializer: configure - ) - } else if let uds = address.unixDomainSocket { - return try await self.connect( - unixDomainSocketPath: uds.path, - channelInitializer: configure - ) - } else if let vsock = address.virtualSocket { - return try await self.connect( - to: VsockAddress( - cid: .init(Int(vsock.contextID.rawValue)), - port: .init(Int(vsock.port.rawValue)) - ), - channelInitializer: configure - ) - } else { - throw RPCError(code: .unimplemented, message: "Unhandled socket address: \(address)") - } - } -} - -extension Metadata { - init(_ sequence: some Sequence) { - var metadata = Metadata() - for (key, value) in sequence { - switch value { - case .string(let value): - metadata.addString(value, forKey: key) - case .binary(let value): - metadata.addBinary(value, forKey: key) - } - } - - self = metadata - } -} diff --git a/Tests/GRPCHTTP2CoreTests/Client/Connection/GRPCChannelTests.swift b/Tests/GRPCHTTP2CoreTests/Client/Connection/GRPCChannelTests.swift deleted file mode 100644 index fc0365703..000000000 --- a/Tests/GRPCHTTP2CoreTests/Client/Connection/GRPCChannelTests.swift +++ /dev/null @@ -1,842 +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 NIOHTTP2 -import NIOPosix -import XCTest - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -final class GRPCChannelTests: XCTestCase { - func testDefaultServiceConfig() throws { - var serviceConfig = ServiceConfig() - serviceConfig.loadBalancingConfig = [.roundRobin] - serviceConfig.methodConfig = [MethodConfig(names: [MethodConfig.Name(.echoGet)])] - serviceConfig.retryThrottling = try ServiceConfig.RetryThrottling( - maxTokens: 100, - tokenRatio: 0.1 - ) - - let channel = GRPCChannel( - resolver: .static(endpoints: []), - connector: .never, - config: .defaults, - defaultServiceConfig: serviceConfig - ) - - XCTAssertNotNil(channel.config(forMethod: .echoGet)) - XCTAssertNil(channel.config(forMethod: .echoUpdate)) - - let throttle = try XCTUnwrap(channel.retryThrottle) - XCTAssertEqual(throttle.maxTokens, 100) - XCTAssertEqual(throttle.tokenRatio, 0.1) - } - - func testServiceConfigFromResolver() async throws { - // Verify that service config from the resolver takes precedence over the default service - // config. This is done indirectly by checking method config and retry throttle config. - - // Create a service config to provide via the resolver. - var serviceConfig = ServiceConfig() - serviceConfig.loadBalancingConfig = [.roundRobin] - serviceConfig.methodConfig = [MethodConfig(names: [MethodConfig.Name(.echoGet)])] - serviceConfig.retryThrottling = try ServiceConfig.RetryThrottling( - maxTokens: 100, - tokenRatio: 0.1 - ) - - // Need a server to connect to, no RPCs will be created though. - let server = TestServer(eventLoopGroup: .singletonMultiThreadedEventLoopGroup) - let address = try await server.bind() - - let channel = GRPCChannel( - resolver: .static(endpoints: [Endpoint(addresses: [address])], serviceConfig: serviceConfig), - connector: .posix(), - config: .defaults, - defaultServiceConfig: ServiceConfig() - ) - - // Not resolved yet so the default (empty) service config is used. - XCTAssertNil(channel.config(forMethod: .echoGet)) - XCTAssertNil(channel.config(forMethod: .echoUpdate)) - XCTAssertNil(channel.retryThrottle) - - try await withThrowingDiscardingTaskGroup { group in - group.addTask { - try await server.run(.never) - } - - group.addTask { - await channel.connect() - } - - for await event in channel.connectivityState { - switch event { - case .ready: - // When the channel is ready it must have the service config from the resolver. - XCTAssertNotNil(channel.config(forMethod: .echoGet)) - XCTAssertNil(channel.config(forMethod: .echoUpdate)) - - let throttle = try XCTUnwrap(channel.retryThrottle) - XCTAssertEqual(throttle.maxTokens, 100) - XCTAssertEqual(throttle.tokenRatio, 0.1) - - // Now close. - channel.beginGracefulShutdown() - - default: - () - } - } - - group.cancelAll() - } - } - - func testServiceConfigFromResolverAfterUpdate() async throws { - // Verify that the channel uses service config from the resolver and that it uses the latest - // version provided by the resolver. This is done indirectly by checking method config and retry - // throttle config. - - let server = TestServer(eventLoopGroup: .singletonMultiThreadedEventLoopGroup) - let address = try await server.bind() - - let (resolver, continuation) = NameResolver.dynamic(updateMode: .push) - let channel = GRPCChannel( - resolver: resolver, - connector: .posix(), - config: .defaults, - defaultServiceConfig: ServiceConfig() - ) - - // Not resolved yet so the default (empty) service config is used. - XCTAssertNil(channel.config(forMethod: .echoGet)) - XCTAssertNil(channel.config(forMethod: .echoUpdate)) - XCTAssertNil(channel.retryThrottle) - - // Yield the first address list and service config. - var serviceConfig = ServiceConfig() - serviceConfig.loadBalancingConfig = [.roundRobin] - serviceConfig.methodConfig = [MethodConfig(names: [MethodConfig.Name(.echoGet)])] - serviceConfig.retryThrottling = try ServiceConfig.RetryThrottling( - maxTokens: 100, - tokenRatio: 0.1 - ) - let resolutionResult = NameResolutionResult( - endpoints: [Endpoint(address)], - serviceConfig: .success(serviceConfig) - ) - continuation.yield(resolutionResult) - - try await withThrowingDiscardingTaskGroup { group in - group.addTask { - try await server.run(.never) - } - - group.addTask { - await channel.connect() - } - - for await event in channel.connectivityState { - switch event { - case .ready: - // When the channel it must have the service config from the resolver. - XCTAssertNotNil(channel.config(forMethod: .echoGet)) - XCTAssertNil(channel.config(forMethod: .echoUpdate)) - let throttle = try XCTUnwrap(channel.retryThrottle) - XCTAssertEqual(throttle.maxTokens, 100) - XCTAssertEqual(throttle.tokenRatio, 0.1) - - // Now yield a new service config with the same addresses. - var resolutionResult = resolutionResult - serviceConfig.methodConfig = [MethodConfig(names: [MethodConfig.Name(.echoUpdate)])] - serviceConfig.retryThrottling = nil - resolutionResult.serviceConfig = .success(serviceConfig) - continuation.yield(resolutionResult) - - // This should be propagated quickly. - try await XCTPoll(every: .milliseconds(10)) { - let noConfigForGet = channel.config(forMethod: .echoGet) == nil - let configForUpdate = channel.config(forMethod: .echoUpdate) != nil - let noThrottle = channel.retryThrottle == nil - return noConfigForGet && configForUpdate && noThrottle - } - - channel.beginGracefulShutdown() - - default: - () - } - } - - group.cancelAll() - } - } - - func testPushBasedResolutionUpdates() async throws { - // Verify that the channel responds to name resolution changes which are pushed into - // the resolver. Do this by starting two servers and only making the address of one available - // via the resolver at a time. Server identity is provided via metadata in the RPC. - - // Start a few servers. - let server1 = TestServer(eventLoopGroup: .singletonMultiThreadedEventLoopGroup) - let address1 = try await server1.bind() - - let server2 = TestServer(eventLoopGroup: .singletonMultiThreadedEventLoopGroup) - let address2 = try await server2.bind() - - // Setup a resolver and push some changes into it. - let (resolver, continuation) = NameResolver.dynamic(updateMode: .push) - let resolution1 = NameResolutionResult(endpoints: [Endpoint(address1)], serviceConfig: nil) - continuation.yield(resolution1) - - var serviceConfig = ServiceConfig() - serviceConfig.loadBalancingConfig = [.roundRobin] - let channel = GRPCChannel( - resolver: resolver, - connector: .posix(), - config: .defaults, - defaultServiceConfig: serviceConfig - ) - - try await withThrowingDiscardingTaskGroup { group in - // Servers respond with their own address in the trailing metadata. - for (server, address) in [(server1, address1), (server2, address2)] { - group.addTask { - try await server.run { inbound, outbound in - let status = Status(code: .ok, message: "") - let metadata: Metadata = ["server-addr": "\(address)"] - try await outbound.write(.status(status, metadata)) - outbound.finish() - } - } - } - - group.addTask { - await channel.connect() - } - - // The stream will be queued until the channel is ready. - let serverAddress1 = try await channel.serverAddress() - XCTAssertEqual(serverAddress1, "\(address1)") - XCTAssertEqual(server1.clients.count, 1) - XCTAssertEqual(server2.clients.count, 0) - - // Yield the second address. Because this happens asynchronously there's no guarantee that - // the next stream will be made against the same server, so poll until the servers have the - // appropriate connections. - let resolution2 = NameResolutionResult(endpoints: [Endpoint(address2)], serviceConfig: nil) - continuation.yield(resolution2) - - try await XCTPoll(every: .milliseconds(10)) { - server1.clients.count == 0 && server2.clients.count == 1 - } - - let serverAddress2 = try await channel.serverAddress() - XCTAssertEqual(serverAddress2, "\(address2)") - - group.cancelAll() - } - } - - func testPullBasedResolutionUpdates() async throws { - // Verify that the channel responds to name resolution changes which are pulled because a - // subchannel asked the channel to re-resolve. Do this by starting two servers and changing - // which is available via resolution updates. Server identity is provided via metadata in - // the RPC. - - // Start a few servers. - let server1 = TestServer(eventLoopGroup: .singletonMultiThreadedEventLoopGroup) - let address1 = try await server1.bind() - - let server2 = TestServer(eventLoopGroup: .singletonMultiThreadedEventLoopGroup) - let address2 = try await server2.bind() - - // Setup a resolve which we push changes into. - let (resolver, continuation) = NameResolver.dynamic(updateMode: .pull) - - // Yield the addresses. - for address in [address1, address2] { - let resolution = NameResolutionResult(endpoints: [Endpoint(address)], serviceConfig: nil) - continuation.yield(resolution) - } - - var serviceConfig = ServiceConfig() - serviceConfig.loadBalancingConfig = [.roundRobin] - let channel = GRPCChannel( - resolver: resolver, - connector: .posix(), - config: .defaults, - defaultServiceConfig: serviceConfig - ) - - try await withThrowingDiscardingTaskGroup { group in - // Servers respond with their own address in the trailing metadata. - for (server, address) in [(server1, address1), (server2, address2)] { - group.addTask { - try await server.run { inbound, outbound in - let status = Status(code: .ok, message: "") - let metadata: Metadata = ["server-addr": "\(address)"] - try await outbound.write(.status(status, metadata)) - outbound.finish() - } - } - } - - group.addTask { - await channel.connect() - } - - // The stream will be queued until the channel is ready. - let serverAddress1 = try await channel.serverAddress() - XCTAssertEqual(serverAddress1, "\(address1)") - XCTAssertEqual(server1.clients.count, 1) - XCTAssertEqual(server2.clients.count, 0) - - // Tell the first server to GOAWAY. This will cause the subchannel to re-resolve. - let server1Client = try XCTUnwrap(server1.clients.first) - let goAway = HTTP2Frame( - streamID: .rootStream, - payload: .goAway(lastStreamID: 1, errorCode: .noError, opaqueData: nil) - ) - try await server1Client.writeAndFlush(goAway) - - // Poll until the first client drops, addresses are re-resolved, and a connection is - // established to server2. - try await XCTPoll(every: .milliseconds(10)) { - server1.clients.count == 0 && server2.clients.count == 1 - } - - let serverAddress2 = try await channel.serverAddress() - XCTAssertEqual(serverAddress2, "\(address2)") - - group.cancelAll() - } - } - - func testCloseWhenRPCsAreInProgress() async throws { - // Verify that closing the channel while there are RPCs in progress allows the RPCs to finish - // gracefully. - - let server = TestServer(eventLoopGroup: .singletonMultiThreadedEventLoopGroup) - let address = try await server.bind() - - try await withThrowingDiscardingTaskGroup { group in - group.addTask { - try await server.run(.echo) - } - - var serviceConfig = ServiceConfig() - serviceConfig.loadBalancingConfig = [.roundRobin] - - let channel = GRPCChannel( - resolver: .static(endpoints: [Endpoint(address)]), - connector: .posix(), - config: .defaults, - defaultServiceConfig: serviceConfig - ) - - group.addTask { - await channel.connect() - } - - try await channel.withStream(descriptor: .echoGet, options: .defaults) { stream in - try await stream.outbound.write(.metadata([:])) - - var iterator = stream.inbound.makeAsyncIterator() - let part1 = try await iterator.next() - switch part1 { - case .metadata: - // Got metadata, close the channel. - channel.beginGracefulShutdown() - case .message, .status, .none: - XCTFail("Expected metadata, got \(String(describing: part1))") - } - - for await state in channel.connectivityState { - switch state { - case .shutdown: - // Happens when shutting-down has been initiated, so finish the RPC. - await stream.outbound.finish() - - let part2 = try await iterator.next() - switch part2 { - case .status(let status, _): - XCTAssertEqual(status.code, .ok) - case .metadata, .message, .none: - XCTFail("Expected status, got \(String(describing: part2))") - } - - default: - () - } - } - } - - group.cancelAll() - } - } - - func testQueueRequestsWhileNotReady() async throws { - // Verify that requests are queued until the channel becomes ready. As creating streams - // will race with the channel becoming ready, we add numerous tasks to the task group which - // each create a stream before making the server address known to the channel via the resolver. - // This isn't perfect as the resolution _could_ happen before attempting to create all streams - // although this is unlikely. - - let server = TestServer(eventLoopGroup: .singletonMultiThreadedEventLoopGroup) - let address = try await server.bind() - - let (resolver, continuation) = NameResolver.dynamic(updateMode: .push) - var serviceConfig = ServiceConfig() - serviceConfig.loadBalancingConfig = [.roundRobin] - let channel = GRPCChannel( - resolver: resolver, - connector: .posix(), - config: .defaults, - defaultServiceConfig: serviceConfig - ) - - enum Subtask { case rpc, other } - try await withThrowingTaskGroup(of: Subtask.self) { group in - // Run the server. - group.addTask { - try await server.run { inbound, outbound in - for try await part in inbound { - switch part { - case .metadata: - try await outbound.write(.metadata([:])) - case .message(let bytes): - try await outbound.write(.message(bytes)) - } - } - - let status = Status(code: .ok, message: "") - try await outbound.write(.status(status, [:])) - outbound.finish() - } - - return .other - } - - group.addTask { - await channel.connect() - return .other - } - - // Start a bunch of requests. These won't start until an address is yielded, they should - // be queued though. - for _ in 1 ... 100 { - group.addTask { - try await channel.withStream(descriptor: .echoGet, options: .defaults) { stream in - try await stream.outbound.write(.metadata([:])) - await stream.outbound.finish() - - for try await part in stream.inbound { - switch part { - case .metadata, .message: - () - case .status(let status, _): - XCTAssertEqual(status.code, .ok) - } - } - } - - return .rpc - } - } - - // At least some of the RPCs should have been queued by now. - let resolution = NameResolutionResult(endpoints: [Endpoint(address)], serviceConfig: nil) - continuation.yield(resolution) - - var outstandingRPCs = 100 - for try await subtask in group { - switch subtask { - case .rpc: - outstandingRPCs -= 1 - - // All RPCs done, close the channel and cancel the group to stop the server. - if outstandingRPCs == 0 { - channel.beginGracefulShutdown() - group.cancelAll() - } - - case .other: - () - } - } - } - } - - func testQueueRequestsFailFast() async throws { - // Verifies that if 'waitsForReady' is 'false', that queued requests are failed when there is - // a transient failure. The transient failure is triggered by attempting to connect to a - // non-existent server. - - let (resolver, continuation) = NameResolver.dynamic(updateMode: .push) - var serviceConfig = ServiceConfig() - serviceConfig.loadBalancingConfig = [.roundRobin] - let channel = GRPCChannel( - resolver: resolver, - connector: .posix(), - config: .defaults, - defaultServiceConfig: serviceConfig - ) - - enum Subtask { case rpc, other } - try await withThrowingTaskGroup(of: Subtask.self) { group in - group.addTask { - await channel.connect() - return .other - } - - for _ in 1 ... 100 { - group.addTask { - var options = CallOptions.defaults - options.waitForReady = false - - await XCTAssertThrowsErrorAsync(ofType: RPCError.self) { - try await channel.withStream(descriptor: .echoGet, options: options) { _ in - XCTFail("Unexpected stream") - } - } errorHandler: { error in - XCTAssertEqual(error.code, .unavailable) - } - - return .rpc - } - } - - // At least some of the RPCs should have been queued by now. - let resolution = NameResolutionResult( - endpoints: [Endpoint(.unixDomainSocket(path: "/test-queue-requests-fail-fast"))], - serviceConfig: nil - ) - continuation.yield(resolution) - - var outstandingRPCs = 100 - for try await subtask in group { - switch subtask { - case .rpc: - outstandingRPCs -= 1 - - // All RPCs done, close the channel and cancel the group to stop the server. - if outstandingRPCs == 0 { - channel.beginGracefulShutdown() - group.cancelAll() - } - - case .other: - () - } - } - } - } - - func testLoadBalancerChangingFromRoundRobinToPickFirst() async throws { - // The test will push different configs to the resolver, first a round-robin LB, then a - // pick-first LB. - let (resolver, continuation) = NameResolver.dynamic(updateMode: .push) - let channel = GRPCChannel( - resolver: resolver, - connector: .posix(), - config: .defaults, - defaultServiceConfig: ServiceConfig() - ) - - // Start a few servers. - let server1 = TestServer(eventLoopGroup: .singletonMultiThreadedEventLoopGroup) - let address1 = try await server1.bind() - - let server2 = TestServer(eventLoopGroup: .singletonMultiThreadedEventLoopGroup) - let address2 = try await server2.bind() - - let server3 = TestServer(eventLoopGroup: .singletonMultiThreadedEventLoopGroup) - let address3 = try await server3.bind() - - try await withThrowingTaskGroup(of: Void.self) { group in - // Run the servers, no RPCs will be run against them. - for server in [server1, server2, server3] { - group.addTask { - try await server.run(.never) - } - } - - group.addTask { - await channel.connect() - } - - for await event in channel.connectivityState { - switch event { - case .idle: - let endpoints = [address1, address2].map { Endpoint(addresses: [$0]) } - var serviceConfig = ServiceConfig() - serviceConfig.loadBalancingConfig = [.roundRobin] - let resolutionResult = NameResolutionResult( - endpoints: endpoints, - serviceConfig: .success(serviceConfig) - ) - - // Push the first resolution result which uses round robin. This will result in the - // channel becoming ready. - continuation.yield(resolutionResult) - - case .ready: - // Channel is ready, server 1 and 2 should have clients shortly. - try await XCTPoll(every: .milliseconds(10)) { - server1.clients.count == 1 && server2.clients.count == 1 && server3.clients.count == 0 - } - - // Both subchannels are ready, prepare and yield an update to the resolver. - var serviceConfig = ServiceConfig() - serviceConfig.loadBalancingConfig = [.pickFirst(shuffleAddressList: false)] - let resolutionResult = NameResolutionResult( - endpoints: [Endpoint(addresses: [address3])], - serviceConfig: .success(serviceConfig) - ) - continuation.yield(resolutionResult) - - // Only server 3 should have a connection. - try await XCTPoll(every: .milliseconds(10)) { - server1.clients.count == 0 && server2.clients.count == 0 && server3.clients.count == 1 - } - - channel.beginGracefulShutdown() - - case .shutdown: - group.cancelAll() - - default: - () - } - } - } - } - - func testPickFirstShufflingAddressList() async throws { - // This test checks that the pick first load-balancer has its address list shuffled. We can't - // assert this deterministically, so instead we'll run an experiment a number of times. Each - // round will create N servers and provide them as endpoints to the pick-first load balancer. - // The channel will establish a connection to one of the servers and its identity will be noted. - let numberOfRounds = 100 - let numberOfServers = 2 - - let servers = (0 ..< numberOfServers).map { _ in - TestServer(eventLoopGroup: .singletonMultiThreadedEventLoopGroup) - } - - var addresses = [SocketAddress]() - for server in servers { - let address = try await server.bind() - addresses.append(address) - } - - let endpoint = Endpoint(addresses: addresses) - var counts = Array(repeating: 0, count: addresses.count) - - // Supply service config on init, not via the load-balancer. - var serviceConfig = ServiceConfig() - serviceConfig.loadBalancingConfig = [.pickFirst(shuffleAddressList: true)] - - try await withThrowingDiscardingTaskGroup { group in - // Run the servers. - for server in servers { - group.addTask { - try await server.run(.never) - } - } - - // Run the experiment. - for _ in 0 ..< numberOfRounds { - let channel = GRPCChannel( - resolver: .static(endpoints: [endpoint]), - connector: .posix(), - config: .defaults, - defaultServiceConfig: serviceConfig - ) - - group.addTask { - await channel.connect() - } - - for await state in channel.connectivityState { - switch state { - case .ready: - for index in servers.indices { - if servers[index].clients.count == 1 { - counts[index] += 1 - break - } - } - channel.beginGracefulShutdown() - default: - () - } - } - } - - // Stop the servers. - group.cancelAll() - } - - // The address list is shuffled, so there's no guarantee how many times we'll hit each server. - // Assert that the minimum a server should be hit is 10% of the time. - let expected = Double(numberOfRounds) / Double(numberOfServers) - let minimum = expected * 0.1 - XCTAssert(counts.allSatisfy({ Double($0) >= minimum }), "\(counts)") - } - - func testPickFirstIsFallbackPolicy() async throws { - // Start a few servers. - let server1 = TestServer(eventLoopGroup: .singletonMultiThreadedEventLoopGroup) - let address1 = try await server1.bind() - - let server2 = TestServer(eventLoopGroup: .singletonMultiThreadedEventLoopGroup) - let address2 = try await server2.bind() - - // Prepare a channel with an empty service config. - let channel = GRPCChannel( - resolver: .static(endpoints: [Endpoint(address1, address2)]), - connector: .posix(), - config: .defaults, - defaultServiceConfig: ServiceConfig() - ) - - try await withThrowingDiscardingTaskGroup { group in - // Run the servers. - for server in [server1, server2] { - group.addTask { - try await server.run(.never) - } - } - - group.addTask { - await channel.connect() - } - - for try await state in channel.connectivityState { - switch state { - case .ready: - // Only server 1 should have a connection. - try await XCTPoll(every: .milliseconds(10)) { - server1.clients.count == 1 && server2.clients.count == 0 - } - - channel.beginGracefulShutdown() - - default: - () - } - } - - group.cancelAll() - } - } - - func testQueueRequestsThenClose() async throws { - // Set a high backoff so the channel stays in transient failure for long enough. - var config = GRPCChannel.Config.defaults - config.backoff.initial = .seconds(120) - - let channel = GRPCChannel( - resolver: .static( - endpoints: [ - Endpoint(.unixDomainSocket(path: "/testQueueRequestsThenClose")) - ] - ), - connector: .posix(), - config: .defaults, - defaultServiceConfig: ServiceConfig() - ) - - try await withThrowingDiscardingTaskGroup { group in - group.addTask { - await channel.connect() - } - - for try await state in channel.connectivityState { - switch state { - case .transientFailure: - group.addTask { - // Sleep a little to increase the chances of the stream being queued before the channel - // reacts to the close. - try await Task.sleep(for: .milliseconds(10)) - channel.beginGracefulShutdown() - } - - // Try to open a new stream. - await XCTAssertThrowsErrorAsync(ofType: RPCError.self) { - try await channel.withStream(descriptor: .echoGet, options: .defaults) { stream in - XCTFail("Unexpected new stream") - } - } errorHandler: { error in - XCTAssertEqual(error.code, .unavailable) - } - - default: - () - } - } - - group.cancelAll() - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension GRPCChannel.Config { - static var defaults: Self { - Self( - http2: .defaults, - backoff: .defaults, - connection: .defaults, - compression: .defaults - ) - } -} - -extension Endpoint { - init(_ addresses: SocketAddress...) { - self.init(addresses: addresses) - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension GRPCChannel { - fileprivate func serverAddress() async throws -> String? { - let values: Metadata.StringValues? = try await self.withStream( - descriptor: .echoGet, - options: .defaults - ) { stream in - try await stream.outbound.write(.metadata([:])) - await stream.outbound.finish() - - for try await part in stream.inbound { - switch part { - case .metadata, .message: - XCTFail("Unexpected part: \(part)") - case .status(_, let metadata): - return metadata[stringValues: "server-addr"] - } - } - return nil - } - - return values?.first(where: { _ in true }) - } -} diff --git a/Tests/GRPCHTTP2CoreTests/Client/Connection/LoadBalancers/LoadBalancerTest.swift b/Tests/GRPCHTTP2CoreTests/Client/Connection/LoadBalancers/LoadBalancerTest.swift deleted file mode 100644 index 571f38436..000000000 --- a/Tests/GRPCHTTP2CoreTests/Client/Connection/LoadBalancers/LoadBalancerTest.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. - */ - -import GRPCHTTP2Core -import XCTest - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -enum LoadBalancerTest { - struct Context { - let servers: [(server: TestServer, address: GRPCHTTP2Core.SocketAddress)] - let loadBalancer: LoadBalancer - } - - static func pickFirst( - servers serverCount: Int, - connector: any HTTP2Connector, - backoff: ConnectionBackoff = .defaults, - timeout: Duration = .seconds(10), - function: String = #function, - handleEvent: @escaping @Sendable (Context, LoadBalancerEvent) async throws -> Void, - verifyEvents: @escaping @Sendable ([LoadBalancerEvent]) -> Void = { _ in } - ) async throws { - try await Self.run( - servers: serverCount, - timeout: timeout, - function: function, - handleEvent: handleEvent, - verifyEvents: verifyEvents - ) { - let pickFirst = PickFirstLoadBalancer( - connector: connector, - backoff: backoff, - defaultCompression: .none, - enabledCompression: .none - ) - return .pickFirst(pickFirst) - } - } - - static func roundRobin( - servers serverCount: Int, - connector: any HTTP2Connector, - backoff: ConnectionBackoff = .defaults, - timeout: Duration = .seconds(10), - function: String = #function, - handleEvent: @escaping @Sendable (Context, LoadBalancerEvent) async throws -> Void, - verifyEvents: @escaping @Sendable ([LoadBalancerEvent]) -> Void = { _ in } - ) async throws { - try await Self.run( - servers: serverCount, - timeout: timeout, - function: function, - handleEvent: handleEvent, - verifyEvents: verifyEvents - ) { - let roundRobin = RoundRobinLoadBalancer( - connector: connector, - backoff: backoff, - defaultCompression: .none, - enabledCompression: .none - ) - return .roundRobin(roundRobin) - } - } - - private static func run( - servers serverCount: Int, - timeout: Duration, - function: String, - handleEvent: @escaping @Sendable (Context, LoadBalancerEvent) async throws -> Void, - verifyEvents: @escaping @Sendable ([LoadBalancerEvent]) -> Void = { _ in }, - makeLoadBalancer: @escaping @Sendable () -> LoadBalancer - ) async throws { - enum TestEvent { - case timedOut - case completed(Result) - } - - try await withThrowingTaskGroup(of: TestEvent.self) { group in - group.addTask { - try? await Task.sleep(for: timeout) - return .timedOut - } - - group.addTask { - do { - try await Self._run( - servers: serverCount, - handleEvent: handleEvent, - verifyEvents: verifyEvents, - makeLoadBalancer: makeLoadBalancer - ) - return .completed(.success(())) - } catch { - return .completed(.failure(error)) - } - } - - let result = try await group.next()! - group.cancelAll() - - switch result { - case .timedOut: - XCTFail("'\(function)' timed out after \(timeout)") - case .completed(let result): - try result.get() - } - } - } - - private static func _run( - servers serverCount: Int, - handleEvent: @escaping @Sendable (Context, LoadBalancerEvent) async throws -> Void, - verifyEvents: @escaping @Sendable ([LoadBalancerEvent]) -> Void, - makeLoadBalancer: @escaping @Sendable () -> LoadBalancer - ) async throws { - try await withThrowingTaskGroup(of: Void.self) { group in - // Create the test servers. - var servers = [(server: TestServer, address: GRPCHTTP2Core.SocketAddress)]() - for _ in 0 ..< serverCount { - let server = TestServer(eventLoopGroup: .singletonMultiThreadedEventLoopGroup) - let address = try await server.bind() - servers.append((server, address)) - - group.addTask { - try await server.run { _, _ in - XCTFail("Unexpected stream") - } - } - } - - // Create the load balancer. - let loadBalancer = makeLoadBalancer() - - group.addTask { - await loadBalancer.run() - } - - let context = Context(servers: servers, loadBalancer: loadBalancer) - - var events = [LoadBalancerEvent]() - for await event in loadBalancer.events { - events.append(event) - try await handleEvent(context, event) - } - - verifyEvents(events) - group.cancelAll() - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension LoadBalancerTest.Context { - var roundRobin: RoundRobinLoadBalancer? { - switch self.loadBalancer { - case .roundRobin(let loadBalancer): - return loadBalancer - case .pickFirst: - return nil - } - } - - var pickFirst: PickFirstLoadBalancer? { - switch self.loadBalancer { - case .roundRobin: - return nil - case .pickFirst(let loadBalancer): - return loadBalancer - } - } -} diff --git a/Tests/GRPCHTTP2CoreTests/Client/Connection/LoadBalancers/PickFirstLoadBalancerTests.swift b/Tests/GRPCHTTP2CoreTests/Client/Connection/LoadBalancers/PickFirstLoadBalancerTests.swift deleted file mode 100644 index 29764adb5..000000000 --- a/Tests/GRPCHTTP2CoreTests/Client/Connection/LoadBalancers/PickFirstLoadBalancerTests.swift +++ /dev/null @@ -1,334 +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 NIOHTTP2 -import NIOPosix -import XCTest - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -final class PickFirstLoadBalancerTests: XCTestCase { - func testPickFirstConnectsToServer() async throws { - try await LoadBalancerTest.pickFirst(servers: 1, connector: .posix()) { context, event in - switch event { - case .connectivityStateChanged(.idle): - let endpoint = Endpoint(addresses: context.servers.map { $0.address }) - context.pickFirst!.updateEndpoint(endpoint) - case .connectivityStateChanged(.ready): - context.loadBalancer.close() - default: - () - } - } verifyEvents: { events in - let expected: [LoadBalancerEvent] = [ - .connectivityStateChanged(.idle), - .connectivityStateChanged(.connecting), - .connectivityStateChanged(.ready), - .connectivityStateChanged(.shutdown), - ] - XCTAssertEqual(events, expected) - } - } - - func testPickSubchannelWhenNotReady() async throws { - try await LoadBalancerTest.pickFirst(servers: 1, connector: .posix()) { context, event in - switch event { - case .connectivityStateChanged(.idle): - XCTAssertNil(context.loadBalancer.pickSubchannel()) - context.loadBalancer.close() - case .connectivityStateChanged(.shutdown): - XCTAssertNil(context.loadBalancer.pickSubchannel()) - default: - () - } - } verifyEvents: { events in - let expected: [LoadBalancerEvent] = [ - .connectivityStateChanged(.idle), - .connectivityStateChanged(.shutdown), - ] - XCTAssertEqual(events, expected) - } - } - - func testPickSubchannelReturnsSameSubchannel() async throws { - try await LoadBalancerTest.pickFirst(servers: 1, connector: .posix()) { context, event in - switch event { - case .connectivityStateChanged(.idle): - let endpoint = Endpoint(addresses: context.servers.map { $0.address }) - context.pickFirst!.updateEndpoint(endpoint) - - case .connectivityStateChanged(.ready): - var ids = Set() - for _ in 0 ..< 100 { - let subchannel = try XCTUnwrap(context.loadBalancer.pickSubchannel()) - ids.insert(subchannel.id) - } - XCTAssertEqual(ids.count, 1) - context.loadBalancer.close() - - default: - () - } - } verifyEvents: { events in - let expected: [LoadBalancerEvent] = [ - .connectivityStateChanged(.idle), - .connectivityStateChanged(.connecting), - .connectivityStateChanged(.ready), - .connectivityStateChanged(.shutdown), - ] - XCTAssertEqual(events, expected) - } - } - - func testEndpointUpdateHandledGracefully() async throws { - try await LoadBalancerTest.pickFirst(servers: 2, connector: .posix()) { context, event in - switch event { - case .connectivityStateChanged(.idle): - let endpoint = Endpoint(addresses: [context.servers[0].address]) - context.pickFirst!.updateEndpoint(endpoint) - - case .connectivityStateChanged(.ready): - // Must be connected to server-0. - try await XCTPoll(every: .milliseconds(10)) { - context.servers[0].server.clients.count == 1 - } - - // Update the endpoint so that it contains server-1. - let endpoint = Endpoint(addresses: [context.servers[1].address]) - context.pickFirst!.updateEndpoint(endpoint) - - // Should remain in the ready state - try await XCTPoll(every: .milliseconds(10)) { - context.servers[0].server.clients.isEmpty && context.servers[1].server.clients.count == 1 - } - - context.loadBalancer.close() - - default: - () - } - } verifyEvents: { events in - let expected: [LoadBalancerEvent] = [ - .connectivityStateChanged(.idle), - .connectivityStateChanged(.connecting), - .connectivityStateChanged(.ready), - .connectivityStateChanged(.shutdown), - ] - XCTAssertEqual(events, expected) - } - } - - func testSameEndpointUpdateIsIgnored() async throws { - try await LoadBalancerTest.pickFirst(servers: 1, connector: .posix()) { context, event in - switch event { - case .connectivityStateChanged(.idle): - let endpoint = Endpoint(addresses: context.servers.map { $0.address }) - context.pickFirst!.updateEndpoint(endpoint) - - case .connectivityStateChanged(.ready): - // Must be connected to server-0. - try await XCTPoll(every: .milliseconds(10)) { - context.servers[0].server.clients.count == 1 - } - - // Update the endpoint. This should be a no-op, server should remain connected. - let endpoint = Endpoint(addresses: context.servers.map { $0.address }) - context.pickFirst!.updateEndpoint(endpoint) - try await XCTPoll(every: .milliseconds(10)) { - context.servers[0].server.clients.count == 1 - } - - context.loadBalancer.close() - - default: - () - } - } verifyEvents: { events in - let expected: [LoadBalancerEvent] = [ - .connectivityStateChanged(.idle), - .connectivityStateChanged(.connecting), - .connectivityStateChanged(.ready), - .connectivityStateChanged(.shutdown), - ] - XCTAssertEqual(events, expected) - } - } - - func testEmptyEndpointUpdateIsIgnored() async throws { - // Checks that an update using the empty endpoint is ignored. - try await LoadBalancerTest.pickFirst(servers: 0, connector: .posix()) { context, event in - switch event { - case .connectivityStateChanged(.idle): - let endpoint = Endpoint(addresses: []) - // Should no-op. - context.pickFirst!.updateEndpoint(endpoint) - context.loadBalancer.close() - - default: - () - } - } verifyEvents: { events in - let expected: [LoadBalancerEvent] = [ - .connectivityStateChanged(.idle), - .connectivityStateChanged(.shutdown), - ] - XCTAssertEqual(events, expected) - } - } - - func testPickOnIdleTriggersConnect() async throws { - // Tests that picking a subchannel when the load balancer is idle triggers a reconnect and - // becomes ready again. Uses a very short idle time to re-enter the idle state. - let idle = AtomicCounter() - - try await LoadBalancerTest.pickFirst( - servers: 1, - connector: .posix(maxIdleTime: .milliseconds(1)) // Aggressively idle the connection - ) { context, event in - switch event { - case .connectivityStateChanged(.idle): - let (_, idleCount) = idle.increment() - - switch idleCount { - case 1: - // The first idle happens when the load balancer in started, give it an endpoint - // which it will connect to. Wait for it to be ready and then idle again. - let endpoint = Endpoint(addresses: context.servers.map { $0.address }) - context.pickFirst!.updateEndpoint(endpoint) - case 2: - // Load-balancer has the endpoints but all are idle. Picking will trigger a connect. - XCTAssertNil(context.loadBalancer.pickSubchannel()) - case 3: - // Connection idled again. Shut it down. - context.loadBalancer.close() - - default: - XCTFail("Became idle too many times") - } - - default: - () - } - } verifyEvents: { events in - let expected: [LoadBalancerEvent] = [ - .connectivityStateChanged(.idle), - .connectivityStateChanged(.connecting), - .connectivityStateChanged(.ready), - .connectivityStateChanged(.idle), - .connectivityStateChanged(.connecting), - .connectivityStateChanged(.ready), - .connectivityStateChanged(.idle), - .connectivityStateChanged(.shutdown), - ] - XCTAssertEqual(events, expected) - } - } - - func testPickFirstConnectionDropReturnsToIdle() async throws { - // Checks that when the load balancers connection is unexpectedly dropped when there are no - // open streams that it returns to the idle state. - let idleCount = AtomicCounter() - - try await LoadBalancerTest.pickFirst(servers: 1, connector: .posix()) { context, event in - switch event { - case .connectivityStateChanged(.idle): - let (_, newIdleCount) = idleCount.increment() - switch newIdleCount { - case 1: - let endpoint = Endpoint(addresses: context.servers.map { $0.address }) - context.pickFirst!.updateEndpoint(endpoint) - case 2: - context.loadBalancer.close() - default: - () - } - - case .connectivityStateChanged(.ready): - // Drop the connection. - context.servers[0].server.clients[0].close(mode: .all, promise: nil) - - default: - () - } - } verifyEvents: { events in - let expected: [LoadBalancerEvent] = [ - .connectivityStateChanged(.idle), - .connectivityStateChanged(.connecting), - .connectivityStateChanged(.ready), - .connectivityStateChanged(.idle), - .connectivityStateChanged(.shutdown), - ] - XCTAssertEqual(events, expected) - } - } - - func testPickFirstReceivesGoAway() async throws { - let idleCount = AtomicCounter() - try await LoadBalancerTest.pickFirst(servers: 2, connector: .posix()) { context, event in - switch event { - case .connectivityStateChanged(.idle): - let (_, newIdleCount) = idleCount.increment() - switch newIdleCount { - case 1: - // Provide the address of the first server. - context.pickFirst!.updateEndpoint(Endpoint(context.servers[0].address)) - case 2: - // Provide the address of the second server. - context.pickFirst!.updateEndpoint(Endpoint(context.servers[1].address)) - default: - () - } - - case .connectivityStateChanged(.ready): - switch idleCount.value { - case 1: - // Must be connected to server 1, send a GOAWAY frame. - let channel = context.servers[0].server.clients.first! - let goAway = HTTP2Frame( - streamID: .rootStream, - payload: .goAway(lastStreamID: 0, errorCode: .noError, opaqueData: nil) - ) - channel.writeAndFlush(goAway, promise: nil) - - case 2: - // Must only be connected to server 2 now. - XCTAssertEqual(context.servers[0].server.clients.count, 0) - XCTAssertEqual(context.servers[1].server.clients.count, 1) - context.loadBalancer.close() - - default: - () - } - - default: - () - } - } verifyEvents: { events in - let expected: [LoadBalancerEvent] = [ - .connectivityStateChanged(.idle), - .connectivityStateChanged(.connecting), - .connectivityStateChanged(.ready), - .requiresNameResolution, - .connectivityStateChanged(.idle), - .connectivityStateChanged(.connecting), - .connectivityStateChanged(.ready), - .connectivityStateChanged(.shutdown), - ] - XCTAssertEqual(events, expected) - } - } -} diff --git a/Tests/GRPCHTTP2CoreTests/Client/Connection/LoadBalancers/RoundRobinLoadBalancerTests.swift b/Tests/GRPCHTTP2CoreTests/Client/Connection/LoadBalancers/RoundRobinLoadBalancerTests.swift deleted file mode 100644 index b53e038b7..000000000 --- a/Tests/GRPCHTTP2CoreTests/Client/Connection/LoadBalancers/RoundRobinLoadBalancerTests.swift +++ /dev/null @@ -1,379 +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 NIOHTTP2 -import NIOPosix -import XCTest - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -final class RoundRobinLoadBalancerTests: XCTestCase { - func testMultipleConnectionsAreEstablished() async throws { - try await LoadBalancerTest.roundRobin(servers: 3, connector: .posix()) { context, event in - switch event { - case .connectivityStateChanged(.idle): - // Update the addresses for the load balancer, this will trigger subchannels to be created - // for each. - let endpoints = context.servers.map { Endpoint(addresses: [$0.address]) } - context.roundRobin!.updateAddresses(endpoints) - - case .connectivityStateChanged(.ready): - // Poll until each server has one connected client. - try await XCTPoll(every: .milliseconds(10)) { - context.servers.allSatisfy { server, _ in server.clients.count == 1 } - } - - // Close to end the test. - context.loadBalancer.close() - - default: - () - } - } verifyEvents: { events in - let expected: [LoadBalancerEvent] = [ - .connectivityStateChanged(.idle), - .connectivityStateChanged(.connecting), - .connectivityStateChanged(.ready), - .connectivityStateChanged(.shutdown), - ] - XCTAssertEqual(events, expected) - } - } - - func testSubchannelsArePickedEvenly() async throws { - try await LoadBalancerTest.roundRobin(servers: 3, connector: .posix()) { context, event in - switch event { - case .connectivityStateChanged(.idle): - // Update the addresses for the load balancer, this will trigger subchannels to be created - // for each. - let endpoints = context.servers.map { Endpoint(addresses: [$0.address]) } - context.roundRobin!.updateAddresses(endpoints) - - case .connectivityStateChanged(.ready): - // Subchannel is ready. This happens when any subchannel becomes ready. Loop until - // we can pick three distinct subchannels. - try await XCTPoll(every: .milliseconds(10)) { - var subchannelIDs = Set() - for _ in 0 ..< 3 { - let subchannel = try XCTUnwrap(context.loadBalancer.pickSubchannel()) - subchannelIDs.insert(subchannel.id) - } - return subchannelIDs.count == 3 - } - - // Now that all are ready, load should be distributed evenly among them. - var counts = [SubchannelID: Int]() - - for round in 1 ... 10 { - for _ in 1 ... 3 { - if let subchannel = context.loadBalancer.pickSubchannel() { - counts[subchannel.id, default: 0] += 1 - } else { - XCTFail("Didn't pick subchannel from ready load balancer") - } - } - - XCTAssertEqual(counts.count, 3, "\(counts)") - XCTAssert(counts.values.allSatisfy({ $0 == round }), "\(counts)") - } - - // Close to finish the test. - context.loadBalancer.close() - - default: - () - } - } verifyEvents: { events in - let expected: [LoadBalancerEvent] = [ - .connectivityStateChanged(.idle), - .connectivityStateChanged(.connecting), - .connectivityStateChanged(.ready), - .connectivityStateChanged(.shutdown), - ] - XCTAssertEqual(events, expected) - } - } - - func testAddressUpdatesAreHandledGracefully() async throws { - try await LoadBalancerTest.roundRobin(servers: 3, connector: .posix()) { context, event in - switch event { - case .connectivityStateChanged(.idle): - // Do the first connect. - let endpoints = [Endpoint(addresses: [context.servers[0].address])] - context.roundRobin!.updateAddresses(endpoints) - - case .connectivityStateChanged(.ready): - // Now the first connection should be established. - do { - try await XCTPoll(every: .milliseconds(10)) { - context.servers[0].server.clients.count == 1 - } - } - - // First connection is okay, add a second. - do { - let endpoints = [ - Endpoint(addresses: [context.servers[0].address]), - Endpoint(addresses: [context.servers[1].address]), - ] - context.roundRobin!.updateAddresses(endpoints) - - try await XCTPoll(every: .milliseconds(10)) { - context.servers.prefix(2).allSatisfy { $0.server.clients.count == 1 } - } - } - - // Remove those two endpoints and add a third. - do { - let endpoints = [Endpoint(addresses: [context.servers[2].address])] - context.roundRobin!.updateAddresses(endpoints) - - try await XCTPoll(every: .milliseconds(10)) { - let disconnected = context.servers.prefix(2).allSatisfy { $0.server.clients.isEmpty } - let connected = context.servers.last!.server.clients.count == 1 - return disconnected && connected - } - } - - context.loadBalancer.close() - - default: - () - } - } verifyEvents: { events in - // Transitioning to new addresses should be graceful, i.e. a complete change shouldn't - // result in dropping away from the ready state. - let expected: [LoadBalancerEvent] = [ - .connectivityStateChanged(.idle), - .connectivityStateChanged(.connecting), - .connectivityStateChanged(.ready), - .connectivityStateChanged(.shutdown), - ] - XCTAssertEqual(events, expected) - } - } - - func testSameAddressUpdatesAreIgnored() async throws { - try await LoadBalancerTest.roundRobin(servers: 3, connector: .posix()) { context, event in - switch event { - case .connectivityStateChanged(.idle): - let endpoints = context.servers.map { _, address in Endpoint(addresses: [address]) } - context.roundRobin!.updateAddresses(endpoints) - - case .connectivityStateChanged(.ready): - // Update with the same addresses, these should be ignored. - let endpoints = context.servers.map { _, address in Endpoint(addresses: [address]) } - context.roundRobin!.updateAddresses(endpoints) - - // We should still have three connections. - try await XCTPoll(every: .milliseconds(10)) { - context.servers.allSatisfy { $0.server.clients.count == 1 } - } - - context.loadBalancer.close() - - default: - () - } - } verifyEvents: { events in - let expected: [LoadBalancerEvent] = [ - .connectivityStateChanged(.idle), - .connectivityStateChanged(.connecting), - .connectivityStateChanged(.ready), - .connectivityStateChanged(.shutdown), - ] - XCTAssertEqual(events, expected) - } - } - - func testEmptyAddressUpdatesAreIgnored() async throws { - try await LoadBalancerTest.roundRobin(servers: 3, connector: .posix()) { context, event in - switch event { - case .connectivityStateChanged(.idle): - let endpoints = context.servers.map { _, address in Endpoint(addresses: [address]) } - context.roundRobin!.updateAddresses(endpoints) - - case .connectivityStateChanged(.ready): - // Update with no-addresses, should be ignored so a subchannel can still be picked. - context.roundRobin!.updateAddresses([]) - - // We should still have three connections. - try await XCTPoll(every: .milliseconds(10)) { - context.servers.allSatisfy { $0.server.clients.count == 1 } - } - - context.loadBalancer.close() - - default: - () - } - } verifyEvents: { events in - let expected: [LoadBalancerEvent] = [ - .connectivityStateChanged(.idle), - .connectivityStateChanged(.connecting), - .connectivityStateChanged(.ready), - .connectivityStateChanged(.shutdown), - ] - XCTAssertEqual(events, expected) - } - } - - func testSubchannelReceivesGoAway() async throws { - try await LoadBalancerTest.roundRobin(servers: 3, connector: .posix()) { context, event in - switch event { - case .connectivityStateChanged(.idle): - // Trigger the connect. - let endpoints = context.servers.map { Endpoint(addresses: [$0.address]) } - context.roundRobin!.updateAddresses(endpoints) - - case .connectivityStateChanged(.ready): - // Wait for all servers to become ready. - try await XCTPoll(every: .milliseconds(10)) { - context.servers.allSatisfy { $0.server.clients.count == 1 } - } - - // The above only checks whether each server has a client, the test relies on all three - // subchannels being ready, poll until we get three distinct IDs. - var ids = Set() - try await XCTPoll(every: .milliseconds(10)) { - for _ in 1 ... 3 { - if let subchannel = context.loadBalancer.pickSubchannel() { - ids.insert(subchannel.id) - } - } - return ids.count == 3 - } - - // Pick the first server and send a GOAWAY to the client. - let client = context.servers[0].server.clients[0] - let goAway = HTTP2Frame( - streamID: .rootStream, - payload: .goAway(lastStreamID: 0, errorCode: .cancel, opaqueData: nil) - ) - - // Send a GOAWAY, this should eventually close the subchannel and trigger a name - // resolution. - client.writeAndFlush(goAway, promise: nil) - - case .requiresNameResolution: - // One subchannel should've been taken out, meaning we can only pick from the remaining two: - let id1 = try XCTUnwrap(context.loadBalancer.pickSubchannel()?.id) - let id2 = try XCTUnwrap(context.loadBalancer.pickSubchannel()?.id) - let id3 = try XCTUnwrap(context.loadBalancer.pickSubchannel()?.id) - XCTAssertNotEqual(id1, id2) - XCTAssertEqual(id1, id3) - - // End the test. - context.loadBalancer.close() - - default: - () - } - } verifyEvents: { events in - let expected: [LoadBalancerEvent] = [ - .connectivityStateChanged(.idle), - .connectivityStateChanged(.connecting), - .connectivityStateChanged(.ready), - .requiresNameResolution, - .connectivityStateChanged(.shutdown), - ] - XCTAssertEqual(events, expected) - } - } - - func testPickSubchannelWhenNotReady() { - let loadBalancer = RoundRobinLoadBalancer( - connector: .never, - backoff: .defaults, - defaultCompression: .none, - enabledCompression: .none - ) - - XCTAssertNil(loadBalancer.pickSubchannel()) - } - - func testPickSubchannelWhenClosed() async { - let loadBalancer = RoundRobinLoadBalancer( - connector: .never, - backoff: .defaults, - defaultCompression: .none, - enabledCompression: .none - ) - - loadBalancer.close() - await loadBalancer.run() - - XCTAssertNil(loadBalancer.pickSubchannel()) - } - - func testPickOnIdleLoadBalancerTriggersConnect() async throws { - let idle = AtomicCounter() - let ready = AtomicCounter() - - try await LoadBalancerTest.roundRobin( - servers: 1, - connector: .posix(maxIdleTime: .milliseconds(25)) // Aggressively idle the connection - ) { context, event in - switch event { - case .connectivityStateChanged(.idle): - let (_, newIdleCount) = idle.increment() - - switch newIdleCount { - case 1: - // The first idle happens when the load balancer in started, give it a set of addresses - // which it will connect to. Wait for it to be ready and then idle again. - let address = context.servers[0].address - let endpoints = [Endpoint(addresses: [address])] - context.roundRobin!.updateAddresses(endpoints) - - case 2: - // Load-balancer has the endpoints but all are idle. Picking will trigger a connect. - XCTAssertNil(context.loadBalancer.pickSubchannel()) - - case 3: - // Connection idled again. Shut it down. - context.loadBalancer.close() - - default: - XCTFail("Became idle too many times") - } - - case .connectivityStateChanged(.ready): - let (_, newReadyCount) = ready.increment() - - if newReadyCount == 2 { - XCTAssertNotNil(context.loadBalancer.pickSubchannel()) - } - - default: - () - } - } verifyEvents: { events in - let expected: [LoadBalancerEvent] = [ - .connectivityStateChanged(.idle), - .connectivityStateChanged(.connecting), - .connectivityStateChanged(.ready), - .connectivityStateChanged(.idle), - .connectivityStateChanged(.connecting), - .connectivityStateChanged(.ready), - .connectivityStateChanged(.idle), - .connectivityStateChanged(.shutdown), - ] - XCTAssertEqual(events, expected) - } - } -} diff --git a/Tests/GRPCHTTP2CoreTests/Client/Connection/LoadBalancers/SubchannelTests.swift b/Tests/GRPCHTTP2CoreTests/Client/Connection/LoadBalancers/SubchannelTests.swift deleted file mode 100644 index b0456aee0..000000000 --- a/Tests/GRPCHTTP2CoreTests/Client/Connection/LoadBalancers/SubchannelTests.swift +++ /dev/null @@ -1,581 +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 NIOCore -import NIOHTTP2 -import NIOPosix -import XCTest - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -final class SubchannelTests: XCTestCase { - func testMakeStreamOnIdleSubchannel() async throws { - let subchannel = self.makeSubchannel( - address: .unixDomainSocket(path: "ignored"), - connector: .never - ) - - await XCTAssertThrowsErrorAsync(ofType: RPCError.self) { - try await subchannel.makeStream(descriptor: .echoGet, options: .defaults) - } errorHandler: { error in - XCTAssertEqual(error.code, .unavailable) - } - - subchannel.shutDown() - } - - func testMakeStreamOnShutdownSubchannel() async throws { - let subchannel = self.makeSubchannel( - address: .unixDomainSocket(path: "ignored"), - connector: .never - ) - - subchannel.shutDown() - await subchannel.run() - - await XCTAssertThrowsErrorAsync(ofType: RPCError.self) { - try await subchannel.makeStream(descriptor: .echoGet, options: .defaults) - } errorHandler: { error in - XCTAssertEqual(error.code, .unavailable) - } - } - - func testMakeStreamOnReadySubchannel() async throws { - let server = TestServer(eventLoopGroup: .singletonMultiThreadedEventLoopGroup) - let address = try await server.bind() - let subchannel = self.makeSubchannel(address: address, connector: .posix()) - - try await withThrowingTaskGroup(of: Void.self) { group in - group.addTask { - try await server.run { inbound, outbound in - for try await part in inbound { - switch part { - case .metadata: - try await outbound.write(.metadata([:])) - case .message(let message): - try await outbound.write(.message(message)) - } - } - try await outbound.write(.status(Status(code: .ok, message: ""), [:])) - } - } - - group.addTask { - await subchannel.run() - } - - subchannel.connect() - - for await event in subchannel.events { - switch event { - case .connectivityStateChanged(.ready): - let stream = try await subchannel.makeStream(descriptor: .echoGet, options: .defaults) - try await stream.execute { inbound, outbound in - try await outbound.write(.metadata([:])) - try await outbound.write(.message([0, 1, 2])) - outbound.finish() - - for try await part in inbound { - switch part { - case .metadata: - () // Don't validate, contains http/2 specific metadata too. - case .message(let message): - XCTAssertEqual(message, [0, 1, 2]) - case .status(let status, _): - XCTAssertEqual(status.code, .ok) - XCTAssertEqual(status.message, "") - } - } - } - subchannel.shutDown() - - default: - () - } - } - - group.cancelAll() - } - } - - func testConnectEventuallySucceeds() async throws { - let path = "test-connect-eventually-succeeds" - let subchannel = self.makeSubchannel( - address: .unixDomainSocket(path: path), - connector: .posix(), - backoff: .fixed(at: .milliseconds(10)) - ) - - await withThrowingTaskGroup(of: Void.self) { group in - group.addTask { await subchannel.run() } - - var hasServer = false - var events = [Subchannel.Event]() - - for await event in subchannel.events { - events.append(event) - switch event { - case .connectivityStateChanged(.idle): - subchannel.connect() - - case .connectivityStateChanged(.transientFailure): - // Don't start more than one server. - if hasServer { continue } - hasServer = true - - group.addTask { - let server = TestServer(eventLoopGroup: .singletonMultiThreadedEventLoopGroup) - _ = try await server.bind(to: .uds(path)) - try await server.run { _, _ in - XCTFail("Unexpected stream") - } - } - - case .connectivityStateChanged(.ready): - subchannel.shutDown() - - case .connectivityStateChanged(.shutdown): - group.cancelAll() - - default: - () - } - } - - // First four events are known: - XCTAssertEqual( - Array(events.prefix(4)), - [ - .connectivityStateChanged(.idle), - .connectivityStateChanged(.connecting), - .connectivityStateChanged(.transientFailure), - .connectivityStateChanged(.connecting), - ] - ) - - // Because there is backoff timing involved, the subchannel may flip from transient failure - // to connecting multiple times. Just check that it eventually becomes ready and is then - // shutdown. - XCTAssertEqual( - Array(events.suffix(2)), - [ - .connectivityStateChanged(.ready), - .connectivityStateChanged(.shutdown), - ] - ) - } - } - - func testConnectIteratesThroughAddresses() async throws { - let server = TestServer(eventLoopGroup: .singletonMultiThreadedEventLoopGroup) - let address = try await server.bind() - let subchannel = self.makeSubchannel( - addresses: [ - .unixDomainSocket(path: "not-listening-1"), - .unixDomainSocket(path: "not-listening-2"), - address, - ], - connector: .posix() - ) - - await withThrowingTaskGroup(of: Void.self) { group in - group.addTask { - try await server.run { _, _ in - XCTFail("Unexpected stream") - } - } - - group.addTask { - await subchannel.run() - } - - for await event in subchannel.events { - switch event { - case .connectivityStateChanged(.idle): - subchannel.connect() - case .connectivityStateChanged(.ready): - subchannel.shutDown() - case .connectivityStateChanged(.shutdown): - group.cancelAll() - default: - () - } - } - } - } - - func testConnectIteratesThroughAddressesWithBackoff() async throws { - let server = TestServer(eventLoopGroup: .singletonMultiThreadedEventLoopGroup) - let udsPath = "test-wrap-around-addrs" - - let subchannel = self.makeSubchannel( - addresses: [ - .unixDomainSocket(path: "not-listening-1"), - .unixDomainSocket(path: "not-listening-2"), - .unixDomainSocket(path: udsPath), - ], - connector: .posix(), - backoff: .fixed(at: .zero) // Skip the backoff period - ) - - try await withThrowingTaskGroup(of: Void.self) { group in - group.addTask { - await subchannel.run() - } - - var isServerRunning = false - - for await event in subchannel.events { - switch event { - case .connectivityStateChanged(.idle): - subchannel.connect() - - case .connectivityStateChanged(.transientFailure): - // The subchannel enters the transient failure state when all addresses have been tried. - // Bind the server now so that the next attempts succeeds. - if isServerRunning { break } - isServerRunning = true - - let address = try await server.bind(to: .uds(udsPath)) - XCTAssertEqual(address, .unixDomainSocket(path: udsPath)) - group.addTask { - try await server.run { _, _ in - XCTFail("Unexpected stream") - } - } - - case .connectivityStateChanged(.ready): - subchannel.shutDown() - - case .connectivityStateChanged(.shutdown): - group.cancelAll() - - default: - () - } - } - } - } - - func testIdleTimeout() async throws { - let server = TestServer(eventLoopGroup: .singletonMultiThreadedEventLoopGroup) - let address = try await server.bind() - let subchannel = self.makeSubchannel( - address: address, - connector: .posix(maxIdleTime: .milliseconds(1)) // Aggressively idle - ) - - await withThrowingTaskGroup(of: Void.self) { group in - group.addTask { - await subchannel.run() - } - - group.addTask { - try await server.run { _, _ in - XCTFail("Unexpected stream") - } - } - - var idleCount = 0 - var events = [Subchannel.Event]() - for await event in subchannel.events { - events.append(event) - switch event { - case .connectivityStateChanged(.idle): - idleCount += 1 - if idleCount == 1 { - subchannel.connect() - } else { - subchannel.shutDown() - } - - case .connectivityStateChanged(.shutdown): - group.cancelAll() - - default: - () - } - } - - let expected: [Subchannel.Event] = [ - .connectivityStateChanged(.idle), - .connectivityStateChanged(.connecting), - .connectivityStateChanged(.ready), - .connectivityStateChanged(.idle), - .connectivityStateChanged(.shutdown), - ] - - XCTAssertEqual(events, expected) - } - } - - func testConnectionDropWhenIdle() async throws { - let server = TestServer(eventLoopGroup: .singletonMultiThreadedEventLoopGroup) - let address = try await server.bind() - let subchannel = self.makeSubchannel(address: address, connector: .posix()) - - await withThrowingTaskGroup(of: Void.self) { group in - group.addTask { - await subchannel.run() - } - - group.addTask { - try await server.run { _, _ in - XCTFail("Unexpected RPC") - } - } - - var events = [Subchannel.Event]() - var idleCount = 0 - - for await event in subchannel.events { - events.append(event) - - switch event { - case .connectivityStateChanged(.idle): - idleCount += 1 - switch idleCount { - case 1: - subchannel.connect() - case 2: - subchannel.shutDown() - default: - XCTFail("Unexpected idle") - } - - case .connectivityStateChanged(.ready): - // Close the connection without a GOAWAY. - server.clients.first?.close(mode: .all, promise: nil) - - case .connectivityStateChanged(.shutdown): - group.cancelAll() - - default: - () - } - } - - let expected: [Subchannel.Event] = [ - .connectivityStateChanged(.idle), - .connectivityStateChanged(.connecting), - .connectivityStateChanged(.ready), - .connectivityStateChanged(.idle), - .connectivityStateChanged(.shutdown), - ] - - XCTAssertEqual(events, expected) - } - } - - func testConnectionDropWithOpenStreams() async throws { - let server = TestServer(eventLoopGroup: .singletonMultiThreadedEventLoopGroup) - let address = try await server.bind() - let subchannel = self.makeSubchannel(address: address, connector: .posix()) - - try await withThrowingTaskGroup(of: Void.self) { group in - group.addTask { - await subchannel.run() - } - - group.addTask { - try await server.run(.echo) - } - - var events = [Subchannel.Event]() - var readyCount = 0 - - for await event in subchannel.events { - events.append(event) - switch event { - case .connectivityStateChanged(.idle): - subchannel.connect() - - case .connectivityStateChanged(.ready): - readyCount += 1 - // When the connection becomes ready the first time, open a stream and forcibly close the - // channel. This will result in an automatic reconnect. Close the subchannel when that - // happens. - if readyCount == 1 { - let stream = try await subchannel.makeStream(descriptor: .echoGet, options: .defaults) - try await stream.execute { inbound, outbound in - try await outbound.write(.metadata([:])) - - // Wait for the metadata to be echo'd back. - var iterator = inbound.makeAsyncIterator() - let _ = try await iterator.next() - - // Stream is definitely open. Bork the connection. - server.clients.first?.close(mode: .all, promise: nil) - - // Wait for the next message which won't arrive, client won't send a message. The - // stream should fail - let _ = try await iterator.next() - } - } else if readyCount == 2 { - subchannel.shutDown() - } - - case .connectivityStateChanged(.shutdown): - group.cancelAll() - - default: - () - } - } - - let expected: [Subchannel.Event] = [ - .connectivityStateChanged(.idle), - .connectivityStateChanged(.connecting), - .connectivityStateChanged(.ready), - .connectivityStateChanged(.transientFailure), - .requiresNameResolution, - .connectivityStateChanged(.connecting), - .connectivityStateChanged(.ready), - .connectivityStateChanged(.shutdown), - ] - - XCTAssertEqual(events, expected) - } - } - - func testConnectedReceivesGoAway() async throws { - let server = TestServer(eventLoopGroup: .singletonMultiThreadedEventLoopGroup) - let address = try await server.bind() - let subchannel = self.makeSubchannel(address: address, connector: .posix()) - - try await withThrowingTaskGroup(of: Void.self) { group in - group.addTask { - try await server.run { _, _ in - XCTFail("Unexpected stream") - } - } - - group.addTask { - await subchannel.run() - } - - var events = [Subchannel.Event]() - - var idleCount = 0 - for await event in subchannel.events { - events.append(event) - - switch event { - case .connectivityStateChanged(.idle): - idleCount += 1 - if idleCount == 1 { - subchannel.connect() - } else if idleCount == 2 { - subchannel.shutDown() - } - - case .connectivityStateChanged(.ready): - // Now the subchannel is ready, send a GOAWAY from the server. - let channel = try XCTUnwrap(server.clients.first) - let goAway = HTTP2Frame( - streamID: .rootStream, - payload: .goAway(lastStreamID: 0, errorCode: .cancel, opaqueData: nil) - ) - try await channel.writeAndFlush(goAway) - - case .connectivityStateChanged(.shutdown): - group.cancelAll() - - default: - () - } - } - - let expectedEvents: [Subchannel.Event] = [ - // Normal connect flow. - .connectivityStateChanged(.idle), - .connectivityStateChanged(.connecting), - .connectivityStateChanged(.ready), - // GOAWAY triggers name resolution and idling. - .goingAway, - .requiresNameResolution, - .connectivityStateChanged(.idle), - // The second idle triggers a close. - .connectivityStateChanged(.shutdown), - ] - - XCTAssertEqual(expectedEvents, events) - } - } - - func testCancelReadySubchannel() async throws { - let server = TestServer(eventLoopGroup: .singletonMultiThreadedEventLoopGroup) - let address = try await server.bind() - let subchannel = self.makeSubchannel(address: address, connector: .posix()) - - await withThrowingTaskGroup(of: Void.self) { group in - group.addTask { - try await server.run { _, _ in - XCTFail("Unexpected stream") - } - } - - group.addTask { - subchannel.connect() - await subchannel.run() - } - - for await event in subchannel.events { - switch event { - case .connectivityStateChanged(.ready): - group.cancelAll() - default: - () - } - } - } - } - - private func makeSubchannel( - addresses: [GRPCHTTP2Core.SocketAddress], - connector: any HTTP2Connector, - backoff: ConnectionBackoff? = nil - ) -> Subchannel { - return Subchannel( - endpoint: Endpoint(addresses: addresses), - id: SubchannelID(), - connector: connector, - backoff: backoff ?? .defaults, - defaultCompression: .none, - enabledCompression: .none - ) - } - - private func makeSubchannel( - address: GRPCHTTP2Core.SocketAddress, - connector: any HTTP2Connector, - backoff: ConnectionBackoff? = nil - ) -> Subchannel { - self.makeSubchannel(addresses: [address], connector: connector, backoff: backoff) - } -} - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -extension ConnectionBackoff { - static func fixed(at interval: Duration, jitter: Double = 0.0) -> Self { - return Self(initial: interval, max: interval, multiplier: 1.0, jitter: jitter) - } - - static var defaults: Self { - ConnectionBackoff(initial: .seconds(10), max: .seconds(120), multiplier: 1.6, jitter: 1.2) - } -} diff --git a/Tests/GRPCHTTP2CoreTests/Client/Connection/RequestQueueTests.swift b/Tests/GRPCHTTP2CoreTests/Client/Connection/RequestQueueTests.swift deleted file mode 100644 index 31b046571..000000000 --- a/Tests/GRPCHTTP2CoreTests/Client/Connection/RequestQueueTests.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. - */ - -import GRPCCore -import Synchronization -import XCTest - -@testable import GRPCHTTP2Core - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -final class RequestQueueTests: XCTestCase { - struct AnErrorToAvoidALeak: Error {} - - func testPopFirstEmpty() { - var queue = RequestQueue() - XCTAssertNil(queue.popFirst()) - } - - func testPopFirstNonEmpty() async { - _ = try? await withCheckedThrowingContinuation { continuation in - var queue = RequestQueue() - let id = QueueEntryID() - - queue.append(continuation: continuation, waitForReady: false, id: id) - guard let popped = queue.popFirst() else { - return XCTFail("Missing continuation") - } - XCTAssertNil(queue.popFirst()) - - popped.resume(throwing: AnErrorToAvoidALeak()) - } - } - - func testPopFirstMultiple() async { - await withTaskGroup(of: QueueEntryID.self) { group in - let queue = SharedRequestQueue() - let signal1 = AsyncStream.makeStream(of: Void.self) - let signal2 = AsyncStream.makeStream(of: Void.self) - - let id1 = QueueEntryID() - let id2 = QueueEntryID() - - group.addTask { - _ = try? await withCheckedThrowingContinuation { continuation in - queue.withQueue { - $0.append(continuation: continuation, waitForReady: false, id: id1) - } - - signal1.continuation.yield() - signal1.continuation.finish() - } - - return id1 - } - - group.addTask { - // Wait until instructed to append. - for await _ in signal1.stream {} - - _ = try? await withCheckedThrowingContinuation { continuation in - queue.withQueue { - $0.append(continuation: continuation, waitForReady: false, id: id2) - } - - signal2.continuation.yield() - signal2.continuation.finish() - } - - return id2 - } - - // Wait for both continuations to be enqueued. - for await _ in signal2.stream {} - - for id in [id1, id2] { - let continuation = queue.withQueue { $0.popFirst() } - continuation?.resume(throwing: AnErrorToAvoidALeak()) - let actual = await group.next() - XCTAssertEqual(id, actual) - } - } - } - - func testRemoveEntryByID() async { - _ = try? await withCheckedThrowingContinuation { continuation in - var queue = RequestQueue() - let id = QueueEntryID() - - queue.append(continuation: continuation, waitForReady: false, id: id) - guard let popped = queue.removeEntry(withID: id) else { - return XCTFail("Missing continuation") - } - XCTAssertNil(queue.removeEntry(withID: id)) - - popped.resume(throwing: AnErrorToAvoidALeak()) - } - } - - func testRemoveEntryByIDMultiple() async { - await withTaskGroup(of: QueueEntryID.self) { group in - let queue = SharedRequestQueue() - let signal1 = AsyncStream.makeStream(of: Void.self) - let signal2 = AsyncStream.makeStream(of: Void.self) - - let id1 = QueueEntryID() - let id2 = QueueEntryID() - - group.addTask { - _ = try? await withCheckedThrowingContinuation { continuation in - queue.withQueue { - $0.append(continuation: continuation, waitForReady: false, id: id1) - } - - signal1.continuation.yield() - signal1.continuation.finish() - } - - return id1 - } - - group.addTask { - // Wait until instructed to append. - for await _ in signal1.stream {} - - _ = try? await withCheckedThrowingContinuation { continuation in - queue.withQueue { - $0.append(continuation: continuation, waitForReady: false, id: id2) - } - - signal2.continuation.yield() - signal2.continuation.finish() - } - - return id2 - } - - // Wait for both continuations to be enqueued. - for await _ in signal2.stream {} - - for id in [id1, id2] { - let continuation = queue.withQueue { $0.removeEntry(withID: id) } - continuation?.resume(throwing: AnErrorToAvoidALeak()) - let actual = await group.next() - XCTAssertEqual(id, actual) - } - } - } - - func testRemoveFastFailingEntries() async throws { - let queue = SharedRequestQueue() - let enqueued = AsyncStream.makeStream(of: Void.self) - - try await withThrowingTaskGroup(of: Void.self) { group in - var waitForReadyIDs = [QueueEntryID]() - var failFastIDs = [QueueEntryID]() - - for _ in 0 ..< 50 { - waitForReadyIDs.append(QueueEntryID()) - failFastIDs.append(QueueEntryID()) - } - - for ids in [waitForReadyIDs, failFastIDs] { - let waitForReady = ids == waitForReadyIDs - for id in ids { - group.addTask { - do { - _ = try await withCheckedThrowingContinuation { continuation in - queue.withQueue { - $0.append(continuation: continuation, waitForReady: waitForReady, id: id) - } - enqueued.continuation.yield() - } - } catch is AnErrorToAvoidALeak { - () - } - } - } - } - - // Wait for all continuations to be enqueued. - var numberEnqueued = 0 - for await _ in enqueued.stream { - numberEnqueued += 1 - if numberEnqueued == (waitForReadyIDs.count + failFastIDs.count) { - enqueued.continuation.finish() - } - } - - // Remove all fast-failing continuations. - let continuations = queue.withQueue { - $0.removeFastFailingEntries() - } - - for continuation in continuations { - continuation.resume(throwing: AnErrorToAvoidALeak()) - } - - for id in failFastIDs { - queue.withQueue { - XCTAssertNil($0.removeEntry(withID: id)) - } - } - - for id in waitForReadyIDs { - let maybeContinuation = queue.withQueue { $0.removeEntry(withID: id) } - let continuation = try XCTUnwrap(maybeContinuation) - continuation.resume(throwing: AnErrorToAvoidALeak()) - } - } - } - - func testRemoveAll() async throws { - let queue = SharedRequestQueue() - let enqueued = AsyncStream.makeStream(of: Void.self) - - await withThrowingTaskGroup(of: Void.self) { group in - for _ in 0 ..< 10 { - group.addTask { - _ = try await withCheckedThrowingContinuation { continuation in - queue.withQueue { - $0.append(continuation: continuation, waitForReady: false, id: QueueEntryID()) - } - - enqueued.continuation.yield() - } - } - } - - // Wait for all continuations to be enqueued. - var numberEnqueued = 0 - for await _ in enqueued.stream { - numberEnqueued += 1 - if numberEnqueued == 10 { - enqueued.continuation.finish() - } - } - - let continuations = queue.withQueue { $0.removeAll() } - XCTAssertEqual(continuations.count, 10) - XCTAssertNil(queue.withQueue { $0.popFirst() }) - - for continuation in continuations { - continuation.resume(throwing: AnErrorToAvoidALeak()) - } - } - } - - final class SharedRequestQueue: Sendable { - private let protectedQueue: Mutex - - init() { - self.protectedQueue = Mutex(RequestQueue()) - } - - func withQueue(_ body: @Sendable (inout RequestQueue) throws -> T) rethrows -> T { - try self.protectedQueue.withLock { - try body(&$0) - } - } - } -} diff --git a/Tests/GRPCHTTP2CoreTests/Client/Connection/Utilities/ConnectionTest.swift b/Tests/GRPCHTTP2CoreTests/Client/Connection/Utilities/ConnectionTest.swift deleted file mode 100644 index aecfd2a8e..000000000 --- a/Tests/GRPCHTTP2CoreTests/Client/Connection/Utilities/ConnectionTest.swift +++ /dev/null @@ -1,219 +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 DequeModule -import GRPCCore -import GRPCHTTP2Core -import NIOCore -import NIOHTTP2 -import NIOPosix - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -enum ConnectionTest { - struct Context { - var server: Server - var connection: Connection - } - - static func run( - connector: any HTTP2Connector, - server mode: Server.Mode = .regular, - handlEvents: ( - _ context: Context, - _ event: Connection.Event - ) async throws -> Void = { _, _ in }, - validateEvents: (_ context: Context, _ events: [Connection.Event]) throws -> Void - ) async throws { - let server = Server(mode: mode) - let address = try await server.bind() - - try await withThrowingTaskGroup(of: Void.self) { group in - let connection = Connection( - address: address, - http2Connector: connector, - defaultCompression: .none, - enabledCompression: .none - ) - let context = Context(server: server, connection: connection) - group.addTask { await connection.run() } - - var events: [Connection.Event] = [] - for await event in connection.events { - events.append(event) - try await handlEvents(context, event) - } - - try validateEvents(context, events) - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension ConnectionTest { - /// A server which only expected to accept a single connection. - final class Server { - private let eventLoop: any EventLoop - private var listener: (any Channel)? - private let client: EventLoopPromise - private let mode: Mode - - enum Mode: Sendable { - case regular - case closeOnAccept - } - - init(mode: Mode) { - self.mode = mode - self.eventLoop = .singletonMultiThreadedEventLoopGroup.next() - self.client = self.eventLoop.next().makePromise() - } - - deinit { - self.listener?.close(promise: nil) - self.client.futureResult.whenSuccess { $0.close(mode: .all, promise: nil) } - } - - var acceptedChannel: any Channel { - get throws { - try self.client.futureResult.wait() - } - } - - func bind() async throws -> GRPCHTTP2Core.SocketAddress { - precondition(self.listener == nil, "\(#function) must only be called once") - - let hasAcceptedChannel = try await self.eventLoop.submit { [loop = self.eventLoop] in - NIOLoopBoundBox(false, eventLoop: loop) - }.get() - - let bootstrap = ServerBootstrap( - group: self.eventLoop - ).childChannelInitializer { [mode = self.mode, client = self.client] channel in - precondition(!hasAcceptedChannel.value, "already accepted a channel") - hasAcceptedChannel.value = true - - switch mode { - case .closeOnAccept: - return channel.close() - - case .regular: - return channel.eventLoop.makeCompletedFuture { - let sync = channel.pipeline.syncOperations - let h2 = NIOHTTP2Handler(mode: .server) - let mux = HTTP2StreamMultiplexer(mode: .server, channel: channel) { stream in - let sync = stream.pipeline.syncOperations - let handler = GRPCServerStreamHandler( - scheme: .http, - acceptedEncodings: .none, - maxPayloadSize: .max, - methodDescriptorPromise: channel.eventLoop.makePromise(of: MethodDescriptor.self) - ) - - return stream.eventLoop.makeCompletedFuture { - try sync.addHandler(handler) - try sync.addHandler(EchoHandler()) - } - } - - try sync.addHandler(h2) - try sync.addHandler(mux) - try sync.addHandlers(SucceedOnSettingsAck(promise: client)) - } - } - } - - let channel = try await bootstrap.bind(host: "127.0.0.1", port: 0).get() - self.listener = channel - return .ipv4(host: "127.0.0.1", port: channel.localAddress!.port!) - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension ConnectionTest { - /// Succeeds a promise when a SETTINGS frame ack has been read. - private final class SucceedOnSettingsAck: ChannelInboundHandler { - typealias InboundIn = HTTP2Frame - typealias InboundOut = HTTP2Frame - - private let promise: EventLoopPromise - - init(promise: EventLoopPromise) { - self.promise = promise - } - - func channelRead(context: ChannelHandlerContext, data: NIOAny) { - let frame = self.unwrapInboundIn(data) - switch frame.payload { - case .settings(.ack): - self.promise.succeed(context.channel) - default: - () - } - - context.fireChannelRead(data) - } - } - - final class EchoHandler: ChannelInboundHandler { - typealias InboundIn = RPCRequestPart - typealias OutboundOut = RPCResponsePart - - private var received: Deque = [] - private var receivedEnd = false - - func userInboundEventTriggered(context: ChannelHandlerContext, event: Any) { - if let event = event as? ChannelEvent, event == .inputClosed { - self.receivedEnd = true - } - } - - func channelRead(context: ChannelHandlerContext, data: NIOAny) { - self.received.append(self.unwrapInboundIn(data)) - } - - func channelReadComplete(context: ChannelHandlerContext) { - while let part = self.received.popFirst() { - switch part { - case .metadata(let metadata): - var filtered = Metadata() - - // Remove any pseudo-headers. - for (key, value) in metadata where !key.hasPrefix(":") { - switch value { - case .string(let value): - filtered.addString(value, forKey: key) - case .binary(let value): - filtered.addBinary(value, forKey: key) - } - } - - context.write(self.wrapOutboundOut(.metadata(filtered)), promise: nil) - - case .message(let message): - context.write(self.wrapOutboundOut(.message(message)), promise: nil) - } - } - - if self.receivedEnd { - let status = Status(code: .ok, message: "") - context.write(self.wrapOutboundOut(.status(status, [:])), promise: nil) - } - - context.flush() - } - } -} diff --git a/Tests/GRPCHTTP2CoreTests/Client/Connection/Utilities/HTTP2Connectors.swift b/Tests/GRPCHTTP2CoreTests/Client/Connection/Utilities/HTTP2Connectors.swift deleted file mode 100644 index 4c08cb018..000000000 --- a/Tests/GRPCHTTP2CoreTests/Client/Connection/Utilities/HTTP2Connectors.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. - */ - -import GRPCCore -import GRPCHTTP2Core -import NIOCore -import NIOHTTP2 -import NIOPosix - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -extension HTTP2Connector where Self == ThrowingConnector { - /// A connector which throws the given error on a connect attempt. - static func throwing(_ error: RPCError) -> Self { - return ThrowingConnector(error: error) - } -} - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -extension HTTP2Connector where Self == NeverConnector { - /// A connector which fatal errors if a connect attempt is made. - static var never: Self { - NeverConnector() - } -} - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -extension HTTP2Connector where Self == NIOPosixConnector { - /// A connector which uses NIOPosix to establish a connection. - static func posix( - maxIdleTime: TimeAmount? = nil, - keepaliveTime: TimeAmount? = nil, - keepaliveTimeout: TimeAmount? = nil, - keepaliveWithoutCalls: Bool = false, - dropPingAcks: Bool = false - ) -> Self { - return NIOPosixConnector( - maxIdleTime: maxIdleTime, - keepaliveTime: keepaliveTime, - keepaliveTimeout: keepaliveTimeout, - keepaliveWithoutCalls: keepaliveWithoutCalls, - dropPingAcks: dropPingAcks - ) - } -} - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -struct ThrowingConnector: HTTP2Connector { - private let error: RPCError - - init(error: RPCError) { - self.error = error - } - - func establishConnection( - to address: GRPCHTTP2Core.SocketAddress - ) async throws -> HTTP2Connection { - throw self.error - } -} - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -struct NeverConnector: HTTP2Connector { - func establishConnection( - to address: GRPCHTTP2Core.SocketAddress - ) async throws -> HTTP2Connection { - fatalError("\(#function) called unexpectedly") - } -} - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -struct NIOPosixConnector: HTTP2Connector { - private let eventLoopGroup: any EventLoopGroup - private let maxIdleTime: TimeAmount? - private let keepaliveTime: TimeAmount? - private let keepaliveTimeout: TimeAmount? - private let keepaliveWithoutCalls: Bool - private let dropPingAcks: Bool - - init( - eventLoopGroup: (any EventLoopGroup)? = nil, - maxIdleTime: TimeAmount? = nil, - keepaliveTime: TimeAmount? = nil, - keepaliveTimeout: TimeAmount? = nil, - keepaliveWithoutCalls: Bool = false, - dropPingAcks: Bool = false - ) { - self.eventLoopGroup = eventLoopGroup ?? .singletonMultiThreadedEventLoopGroup - self.maxIdleTime = maxIdleTime - self.keepaliveTime = keepaliveTime - self.keepaliveTimeout = keepaliveTimeout - self.keepaliveWithoutCalls = keepaliveWithoutCalls - self.dropPingAcks = dropPingAcks - } - - func establishConnection( - to address: GRPCHTTP2Core.SocketAddress - ) async throws -> HTTP2Connection { - return try await ClientBootstrap(group: self.eventLoopGroup).connect(to: address) { channel in - channel.eventLoop.makeCompletedFuture { - let sync = channel.pipeline.syncOperations - - let connectionHandler = ClientConnectionHandler( - eventLoop: channel.eventLoop, - maxIdleTime: self.maxIdleTime, - keepaliveTime: self.keepaliveTime, - keepaliveTimeout: self.keepaliveTimeout, - keepaliveWithoutCalls: self.keepaliveWithoutCalls - ) - - let multiplexer = try sync.configureAsyncHTTP2Pipeline( - mode: .client, - streamDelegate: connectionHandler.http2StreamDelegate - ) { stream in - // Server shouldn't be opening streams. - stream.close() - } - - if self.dropPingAcks { - try sync.addHandler(PingAckDropper()) - } - - try sync.addHandler(connectionHandler) - - let asyncChannel = try NIOAsyncChannel( - wrappingChannelSynchronously: channel - ) - - return HTTP2Connection(channel: asyncChannel, multiplexer: multiplexer, isPlaintext: true) - } - } - } - - /// Drops all acks for PING frames. This is useful to help trigger the keepalive timeout. - final class PingAckDropper: ChannelInboundHandler { - typealias InboundIn = HTTP2Frame - typealias InboundOut = HTTP2Frame - - func channelRead(context: ChannelHandlerContext, data: NIOAny) { - let frame = self.unwrapInboundIn(data) - switch frame.payload { - case .ping(_, ack: true): - () // drop-it - default: - context.fireChannelRead(data) - } - } - } -} diff --git a/Tests/GRPCHTTP2CoreTests/Client/Connection/Utilities/NameResolvers.swift b/Tests/GRPCHTTP2CoreTests/Client/Connection/Utilities/NameResolvers.swift deleted file mode 100644 index 9d3973025..000000000 --- a/Tests/GRPCHTTP2CoreTests/Client/Connection/Utilities/NameResolvers.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 GRPCCore -import GRPCHTTP2Core - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension NameResolver { - static func `static`( - endpoints: [Endpoint], - serviceConfig: ServiceConfig? = nil - ) -> Self { - let result = NameResolutionResult( - endpoints: endpoints, - serviceConfig: serviceConfig.map { .success($0) } - ) - - return NameResolver( - names: RPCAsyncSequence(wrapping: ConstantAsyncSequence(element: result)), - updateMode: .pull - ) - } - - static func `dynamic`( - updateMode: UpdateMode - ) -> (Self, AsyncThrowingStream.Continuation) { - let (stream, continuation) = AsyncThrowingStream.makeStream(of: NameResolutionResult.self) - let resolver = NameResolver(names: RPCAsyncSequence(wrapping: stream), updateMode: updateMode) - return (resolver, continuation) - } -} - -@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) -struct ConstantAsyncSequence: AsyncSequence, Sendable { - private let result: Result - - init(element: Element) { - self.result = .success(element) - } - - init(error: any Error) { - self.result = .failure(error) - } - - func makeAsyncIterator() -> AsyncIterator { - AsyncIterator(result: self.result) - } - - struct AsyncIterator: AsyncIteratorProtocol { - private let result: Result - - fileprivate init(result: Result) { - self.result = result - } - - func next() async throws -> Element? { - try self.result.get() - } - } - -} diff --git a/Tests/GRPCHTTP2CoreTests/Client/Connection/Utilities/TestServer.swift b/Tests/GRPCHTTP2CoreTests/Client/Connection/Utilities/TestServer.swift deleted file mode 100644 index 37292e4ea..000000000 --- a/Tests/GRPCHTTP2CoreTests/Client/Connection/Utilities/TestServer.swift +++ /dev/null @@ -1,178 +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 NIOCore -import NIOHTTP2 -import NIOPosix -import Synchronization -import XCTest - -@testable import GRPCHTTP2Core - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -final class TestServer: Sendable { - private let eventLoopGroup: any EventLoopGroup - private typealias Stream = NIOAsyncChannel - private typealias Multiplexer = NIOHTTP2AsyncSequence - - private let connected: Mutex<[any Channel]> - - typealias Inbound = NIOAsyncChannelInboundStream - typealias Outbound = NIOAsyncChannelOutboundWriter - - private let server: Mutex?> - - init(eventLoopGroup: any EventLoopGroup) { - self.eventLoopGroup = eventLoopGroup - self.server = Mutex(nil) - self.connected = Mutex([]) - } - - enum Target { - case localhost - case uds(String) - } - - var clients: [any Channel] { - return self.connected.withLock { $0 } - } - - func bind(to target: Target = .localhost) async throws -> GRPCHTTP2Core.SocketAddress { - precondition(self.server.withLock { $0 } == nil) - - @Sendable - func configure(_ channel: any Channel) -> EventLoopFuture { - self.connected.withLock { - $0.append(channel) - } - - channel.closeFuture.whenSuccess { - self.connected.withLock { connected in - guard let index = connected.firstIndex(where: { $0 === channel }) else { return } - connected.remove(at: index) - } - } - - return channel.eventLoop.makeCompletedFuture { - let sync = channel.pipeline.syncOperations - let multiplexer = try sync.configureAsyncHTTP2Pipeline(mode: .server) { stream in - stream.eventLoop.makeCompletedFuture { - let handler = GRPCServerStreamHandler( - scheme: .http, - acceptedEncodings: .all, - maxPayloadSize: .max, - methodDescriptorPromise: channel.eventLoop.makePromise(of: MethodDescriptor.self) - ) - - try stream.pipeline.syncOperations.addHandlers(handler) - return try NIOAsyncChannel( - wrappingChannelSynchronously: stream, - configuration: .init( - inboundType: RPCRequestPart.self, - outboundType: RPCResponsePart.self - ) - ) - } - } - - return multiplexer.inbound - } - } - - let bootstrap = ServerBootstrap(group: self.eventLoopGroup) - let server: NIOAsyncChannel - let address: GRPCHTTP2Core.SocketAddress - - switch target { - case .localhost: - server = try await bootstrap.bind(host: "127.0.0.1", port: 0) { channel in - configure(channel) - } - address = .ipv4(host: "127.0.0.1", port: server.channel.localAddress!.port!) - - case .uds(let path): - server = try await bootstrap.bind(unixDomainSocketPath: path, cleanupExistingSocketFile: true) - { channel in - configure(channel) - } - address = .unixDomainSocket(path: server.channel.localAddress!.pathname!) - } - - self.server.withLock { $0 = server } - return address - } - - func run(_ handle: @Sendable @escaping (Inbound, Outbound) async throws -> Void) async throws { - guard let server = self.server.withLock({ $0 }) else { - fatalError("bind() must be called first") - } - - do { - try await server.executeThenClose { inbound, _ in - try await withThrowingTaskGroup(of: Void.self) { multiplexerGroup in - for try await multiplexer in inbound { - multiplexerGroup.addTask { - try await withThrowingTaskGroup(of: Void.self) { streamGroup in - for try await stream in multiplexer { - streamGroup.addTask { - try await stream.executeThenClose { inbound, outbound in - try await handle(inbound, outbound) - } - } - } - } - } - } - } - } - } catch is CancellationError { - () - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension TestServer { - enum RunHandler { - case echo - case never - } - - func run(_ handler: RunHandler) async throws { - switch handler { - case .echo: - try await self.run { inbound, outbound in - for try await part in inbound { - switch part { - case .metadata: - try await outbound.write(.metadata([:])) - case .message(let bytes): - try await outbound.write(.message(bytes)) - } - } - try await outbound.write(.status(Status(code: .ok, message: ""), [:])) - } - - case .never: - try await self.run { inbound, outbound in - XCTFail("Unexpected stream") - try await outbound.write(.status(Status(code: .unavailable, message: ""), [:])) - outbound.finish() - } - } - } -} diff --git a/Tests/GRPCHTTP2CoreTests/Client/GRPCClientStreamHandlerTests.swift b/Tests/GRPCHTTP2CoreTests/Client/GRPCClientStreamHandlerTests.swift deleted file mode 100644 index 590153380..000000000 --- a/Tests/GRPCHTTP2CoreTests/Client/GRPCClientStreamHandlerTests.swift +++ /dev/null @@ -1,942 +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 NIOCore -import NIOEmbedded -import NIOHPACK -import NIOHTTP1 -import NIOHTTP2 -import XCTest - -@testable import GRPCHTTP2Core - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -final class GRPCClientStreamHandlerTests: XCTestCase { - func testH2FramesAreIgnored() throws { - let handler = GRPCClientStreamHandler( - methodDescriptor: .init(service: "test", method: "test"), - scheme: .http, - outboundEncoding: .none, - acceptedEncodings: [], - maxPayloadSize: 1 - ) - - let channel = EmbeddedChannel(handler: handler) - - let framesToBeIgnored: [HTTP2Frame.FramePayload] = [ - .ping(.init(), ack: false), - .goAway(lastStreamID: .rootStream, errorCode: .cancel, opaqueData: nil), - // TODO: uncomment when it's possible to build a `StreamPriorityData`. - // .priority( - // HTTP2Frame.StreamPriorityData(exclusive: false, dependency: .rootStream, weight: 4) - // ), - .settings(.ack), - .pushPromise(.init(pushedStreamID: .maxID, headers: [:])), - .windowUpdate(windowSizeIncrement: 4), - .alternativeService(origin: nil, field: nil), - .origin([]), - ] - - for toBeIgnored in framesToBeIgnored { - XCTAssertNoThrow(try channel.writeInbound(toBeIgnored)) - XCTAssertNil(try channel.readInbound(as: HTTP2Frame.FramePayload.self)) - } - } - - func testServerInitialMetadataMissingHTTPStatusCodeResultsInFinishedRPC() throws { - let handler = GRPCClientStreamHandler( - methodDescriptor: .init(service: "test", method: "test"), - scheme: .http, - outboundEncoding: .none, - acceptedEncodings: [], - maxPayloadSize: 1, - skipStateMachineAssertions: true - ) - - let channel = EmbeddedChannel(handler: handler) - - // Send client's initial metadata - let request = RPCRequestPart.metadata([:]) - XCTAssertNoThrow(try channel.writeOutbound(request)) - - // Receive server's initial metadata without :status - let serverInitialMetadata: HPACKHeaders = [ - GRPCHTTP2Keys.contentType.rawValue: ContentType.grpc.canonicalValue - ] - - XCTAssertNoThrow( - try channel.writeInbound( - HTTP2Frame.FramePayload.headers(.init(headers: serverInitialMetadata)) - ) - ) - - XCTAssertEqual( - try channel.readInbound(as: RPCResponsePart.self), - .status( - .init(code: .unknown, message: "HTTP Status Code is missing."), - Metadata(headers: serverInitialMetadata) - ) - ) - } - - func testServerInitialMetadata1xxHTTPStatusCodeResultsInNothingRead() throws { - let handler = GRPCClientStreamHandler( - methodDescriptor: .init(service: "test", method: "test"), - scheme: .http, - outboundEncoding: .none, - acceptedEncodings: [], - maxPayloadSize: 1, - skipStateMachineAssertions: true - ) - - let channel = EmbeddedChannel(handler: handler) - - // Send client's initial metadata - let request = RPCRequestPart.metadata([:]) - XCTAssertNoThrow(try channel.writeOutbound(request)) - - // Receive server's initial metadata with 1xx status - let serverInitialMetadata: HPACKHeaders = [ - GRPCHTTP2Keys.status.rawValue: "104", - GRPCHTTP2Keys.contentType.rawValue: ContentType.grpc.canonicalValue, - ] - - XCTAssertNoThrow( - try channel.writeInbound( - HTTP2Frame.FramePayload.headers(.init(headers: serverInitialMetadata)) - ) - ) - - XCTAssertNil(try channel.readInbound(as: RPCResponsePart.self)) - } - - func testServerInitialMetadataOtherNon200HTTPStatusCodeResultsInFinishedRPC() throws { - let handler = GRPCClientStreamHandler( - methodDescriptor: .init(service: "test", method: "test"), - scheme: .http, - outboundEncoding: .none, - acceptedEncodings: [], - maxPayloadSize: 1, - skipStateMachineAssertions: true - ) - - let channel = EmbeddedChannel(handler: handler) - - // Send client's initial metadata - let request = RPCRequestPart.metadata([:]) - XCTAssertNoThrow(try channel.writeOutbound(request)) - - // Receive server's initial metadata with non-200 and non-1xx :status - let serverInitialMetadata: HPACKHeaders = [ - GRPCHTTP2Keys.status.rawValue: String(HTTPResponseStatus.tooManyRequests.code), - GRPCHTTP2Keys.contentType.rawValue: ContentType.grpc.canonicalValue, - ] - - XCTAssertNoThrow( - try channel.writeInbound( - HTTP2Frame.FramePayload.headers(.init(headers: serverInitialMetadata)) - ) - ) - - XCTAssertEqual( - try channel.readInbound(as: RPCResponsePart.self), - .status( - .init(code: .unavailable, message: "Unexpected non-200 HTTP Status Code."), - Metadata(headers: serverInitialMetadata) - ) - ) - } - - func testServerInitialMetadataMissingContentTypeResultsInFinishedRPC() throws { - let handler = GRPCClientStreamHandler( - methodDescriptor: .init(service: "test", method: "test"), - scheme: .http, - outboundEncoding: .none, - acceptedEncodings: [], - maxPayloadSize: 1, - skipStateMachineAssertions: true - ) - - let channel = EmbeddedChannel(handler: handler) - - // Send client's initial metadata - let request = RPCRequestPart.metadata([:]) - XCTAssertNoThrow(try channel.writeOutbound(request)) - - // Receive server's initial metadata without content-type - let serverInitialMetadata: HPACKHeaders = [ - GRPCHTTP2Keys.status.rawValue: "200" - ] - - XCTAssertNoThrow( - try channel.writeInbound( - HTTP2Frame.FramePayload.headers(.init(headers: serverInitialMetadata)) - ) - ) - - XCTAssertEqual( - try channel.readInbound(as: RPCResponsePart.self), - .status( - .init(code: .internalError, message: "Missing content-type header"), - Metadata(headers: serverInitialMetadata) - ) - ) - } - - func testNotAcceptedEncodingResultsInFinishedRPC() throws { - let handler = GRPCClientStreamHandler( - methodDescriptor: .init(service: "test", method: "test"), - scheme: .http, - outboundEncoding: .deflate, - acceptedEncodings: [.deflate], - maxPayloadSize: 1 - ) - - let channel = EmbeddedChannel(handler: handler) - - // Send client's initial metadata - XCTAssertNoThrow( - try channel.writeOutbound(RPCRequestPart.metadata(Metadata())) - ) - - // Make sure we have sent right metadata. - let writtenMetadata = try channel.assertReadHeadersOutbound() - - XCTAssertEqual( - writtenMetadata.headers, - [ - GRPCHTTP2Keys.method.rawValue: "POST", - GRPCHTTP2Keys.scheme.rawValue: "http", - GRPCHTTP2Keys.path.rawValue: "/test/test", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - GRPCHTTP2Keys.te.rawValue: "trailers", - GRPCHTTP2Keys.encoding.rawValue: "deflate", - GRPCHTTP2Keys.acceptEncoding.rawValue: "deflate", - ] - ) - - // Server sends initial metadata with unsupported encoding - let serverInitialMetadata: HPACKHeaders = [ - GRPCHTTP2Keys.status.rawValue: "200", - GRPCHTTP2Keys.contentType.rawValue: ContentType.grpc.canonicalValue, - GRPCHTTP2Keys.encoding.rawValue: "gzip", - ] - - XCTAssertNoThrow( - try channel.writeInbound( - HTTP2Frame.FramePayload.headers(.init(headers: serverInitialMetadata)) - ) - ) - - XCTAssertEqual( - try channel.readInbound(as: RPCResponsePart.self), - .status( - .init( - code: .internalError, - message: - "The server picked a compression algorithm ('gzip') the client does not know about." - ), - Metadata(headers: serverInitialMetadata) - ) - ) - } - - func testOverMaximumPayloadSize() throws { - let handler = GRPCClientStreamHandler( - methodDescriptor: .init(service: "test", method: "test"), - scheme: .http, - outboundEncoding: .none, - acceptedEncodings: [], - maxPayloadSize: 1, - skipStateMachineAssertions: true - ) - - let channel = EmbeddedChannel(handler: handler) - - // Send client's initial metadata - XCTAssertNoThrow( - try channel.writeOutbound(RPCRequestPart.metadata(Metadata())) - ) - - // Make sure we have sent right metadata. - let writtenMetadata = try channel.assertReadHeadersOutbound() - - XCTAssertEqual( - writtenMetadata.headers, - [ - GRPCHTTP2Keys.method.rawValue: "POST", - GRPCHTTP2Keys.scheme.rawValue: "http", - GRPCHTTP2Keys.path.rawValue: "/test/test", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - GRPCHTTP2Keys.te.rawValue: "trailers", - ] - ) - - // Server sends initial metadata - let serverInitialMetadata: HPACKHeaders = [ - GRPCHTTP2Keys.status.rawValue: "200", - GRPCHTTP2Keys.contentType.rawValue: ContentType.grpc.canonicalValue, - ] - XCTAssertNoThrow( - try channel.writeInbound( - HTTP2Frame.FramePayload.headers(.init(headers: serverInitialMetadata)) - ) - ) - XCTAssertEqual( - try channel.readInbound(as: RPCResponsePart.self), - .metadata(Metadata(headers: serverInitialMetadata)) - ) - - // Server sends message over payload limit - var buffer = ByteBuffer() - buffer.writeInteger(UInt8(0)) // not compressed - buffer.writeInteger(UInt32(42)) // message length - buffer.writeRepeatingByte(0, count: 42) // message - let clientDataPayload = HTTP2Frame.FramePayload.Data( - data: .byteBuffer(buffer), - endStream: false - ) - - // Invalid payload should result in error status and stream being closed - try channel.writeInbound(HTTP2Frame.FramePayload.data(clientDataPayload)) - let part = try channel.readInbound(as: RPCResponsePart.self) - XCTAssertEqual( - part, - .status(Status(code: .internalError, message: "Failed to decode message"), [:]) - ) - channel.embeddedEventLoop.run() - try channel.closeFuture.wait() - } - - func testServerSendsEOSWhenSendingMessage_ResultsInErrorStatus() throws { - let handler = GRPCClientStreamHandler( - methodDescriptor: .init(service: "test", method: "test"), - scheme: .http, - outboundEncoding: .none, - acceptedEncodings: [], - maxPayloadSize: 100, - skipStateMachineAssertions: true - ) - - let channel = EmbeddedChannel(handler: handler) - - // Send client's initial metadata - XCTAssertNoThrow( - try channel.writeOutbound(RPCRequestPart.metadata(Metadata())) - ) - - // Make sure we have sent right metadata. - let writtenMetadata = try channel.assertReadHeadersOutbound() - - XCTAssertEqual( - writtenMetadata.headers, - [ - GRPCHTTP2Keys.method.rawValue: "POST", - GRPCHTTP2Keys.scheme.rawValue: "http", - GRPCHTTP2Keys.path.rawValue: "/test/test", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - GRPCHTTP2Keys.te.rawValue: "trailers", - ] - ) - - // Server sends initial metadata - let serverInitialMetadata: HPACKHeaders = [ - GRPCHTTP2Keys.status.rawValue: "200", - GRPCHTTP2Keys.contentType.rawValue: ContentType.grpc.canonicalValue, - ] - XCTAssertNoThrow( - try channel.writeInbound( - HTTP2Frame.FramePayload.headers(.init(headers: serverInitialMetadata)) - ) - ) - XCTAssertEqual( - try channel.readInbound(as: RPCResponsePart.self), - .metadata(Metadata(headers: serverInitialMetadata)) - ) - - // Server sends message with EOS set. - var buffer = ByteBuffer() - buffer.writeInteger(UInt8(0)) // not compressed - buffer.writeInteger(UInt32(42)) // message length - buffer.writeRepeatingByte(0, count: 42) // message - let clientDataPayload = HTTP2Frame.FramePayload.Data(data: .byteBuffer(buffer), endStream: true) - XCTAssertNoThrow(try channel.writeInbound(HTTP2Frame.FramePayload.data(clientDataPayload))) - - // Make sure we got status + trailers with the right error. - XCTAssertEqual( - try channel.readInbound(as: RPCResponsePart.self), - .status( - Status( - code: .internalError, - message: - "Server sent EOS alongside a data frame, but server is only allowed to close by sending status and trailers." - ), - [:] - ) - ) - } - - func testServerEndsStream() throws { - let handler = GRPCClientStreamHandler( - methodDescriptor: .init(service: "test", method: "test"), - scheme: .http, - outboundEncoding: .none, - acceptedEncodings: [], - maxPayloadSize: 1, - skipStateMachineAssertions: true - ) - - let channel = EmbeddedChannel(handler: handler) - - // Write client's initial metadata - XCTAssertNoThrow(try channel.writeOutbound(RPCRequestPart.metadata(Metadata()))) - let clientInitialMetadata: HPACKHeaders = [ - GRPCHTTP2Keys.path.rawValue: "/test/test", - GRPCHTTP2Keys.scheme.rawValue: "http", - GRPCHTTP2Keys.method.rawValue: "POST", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - GRPCHTTP2Keys.te.rawValue: "trailers", - ] - let writtenInitialMetadata = try channel.assertReadHeadersOutbound() - XCTAssertEqual(writtenInitialMetadata.headers, clientInitialMetadata) - - // Receive server's initial metadata with end stream set - let serverInitialMetadata: HPACKHeaders = [ - GRPCHTTP2Keys.status.rawValue: "200", - GRPCHTTP2Keys.grpcStatus.rawValue: "0", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - ] - XCTAssertNoThrow( - try channel.writeInbound( - HTTP2Frame.FramePayload.headers( - .init( - headers: serverInitialMetadata, - endStream: true - ) - ) - ) - ) - XCTAssertEqual( - try channel.readInbound(as: RPCResponsePart.self), - .status( - .init(code: .ok, message: ""), - [ - GRPCHTTP2Keys.status.rawValue: "200", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - ] - ) - ) - - // We should throw if the server sends another message, since it's closed the stream already. - var buffer = ByteBuffer() - buffer.writeInteger(UInt8(0)) // not compressed - buffer.writeInteger(UInt32(42)) // message length - buffer.writeRepeatingByte(0, count: 42) // message - let serverDataPayload = HTTP2Frame.FramePayload.Data(data: .byteBuffer(buffer), endStream: true) - XCTAssertThrowsError( - ofType: RPCError.self, - try channel.writeInbound(HTTP2Frame.FramePayload.data(serverDataPayload)) - ) { error in - XCTAssertEqual(error.code, .internalError) - XCTAssertEqual(error.message, "Invalid state") - } - } - - func testNormalFlow() throws { - let handler = GRPCClientStreamHandler( - methodDescriptor: .init(service: "test", method: "test"), - scheme: .http, - outboundEncoding: .none, - acceptedEncodings: [], - maxPayloadSize: 100, - skipStateMachineAssertions: true - ) - - let channel = EmbeddedChannel(handler: handler) - - // Send client's initial metadata - let request = RPCRequestPart.metadata([:]) - XCTAssertNoThrow(try channel.writeOutbound(request)) - - // Make sure we have sent the corresponding frame, and that nothing has been written back. - let writtenHeaders = try channel.assertReadHeadersOutbound() - XCTAssertEqual( - writtenHeaders.headers, - [ - GRPCHTTP2Keys.method.rawValue: "POST", - GRPCHTTP2Keys.scheme.rawValue: "http", - GRPCHTTP2Keys.path.rawValue: "/test/test", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - GRPCHTTP2Keys.te.rawValue: "trailers", - - ] - ) - XCTAssertNil(try channel.readInbound(as: RPCResponsePart.self)) - - // Receive server's initial metadata - let serverInitialMetadata: HPACKHeaders = [ - GRPCHTTP2Keys.status.rawValue: "200", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - "some-custom-header": "some-custom-value", - ] - XCTAssertNoThrow( - try channel.writeInbound( - HTTP2Frame.FramePayload.headers(.init(headers: serverInitialMetadata)) - ) - ) - - XCTAssertEqual( - try channel.readInbound(as: RPCResponsePart.self), - RPCResponsePart.metadata(Metadata(headers: serverInitialMetadata)) - ) - - // Send a message - XCTAssertNoThrow( - try channel.writeOutbound(RPCRequestPart.message(.init(repeating: 1, count: 42))) - ) - - // Assert we wrote it successfully into the channel - let writtenMessage = try channel.assertReadDataOutbound() - var expectedBuffer = ByteBuffer() - expectedBuffer.writeInteger(UInt8(0)) // not compressed - expectedBuffer.writeInteger(UInt32(42)) // message length - expectedBuffer.writeRepeatingByte(1, count: 42) // message - XCTAssertEqual(writtenMessage.data, .byteBuffer(expectedBuffer)) - - // Half-close the outbound end: this would be triggered by finishing the client's writer. - XCTAssertNoThrow(channel.close(mode: .output, promise: nil)) - - // Flush to make sure the EOS is written. - channel.flush() - - // Make sure the EOS frame was sent - let emptyEOSFrame = try channel.assertReadDataOutbound() - XCTAssertEqual(emptyEOSFrame.data, .byteBuffer(.init())) - XCTAssertTrue(emptyEOSFrame.endStream) - - // Make sure we cannot write anymore because client's closed. - XCTAssertThrowsError( - ofType: RPCError.self, - try channel.writeOutbound(RPCRequestPart.message(.init(repeating: 1, count: 42))) - ) { error in - XCTAssertEqual(error.code, .internalError) - XCTAssertEqual(error.message, "Invalid state") - } - - // This is needed to clear the EmbeddedChannel's stored error, otherwise - // it will be thrown when writing inbound. - try? channel.throwIfErrorCaught() - - // Server sends back response message - var buffer = ByteBuffer() - buffer.writeInteger(UInt8(0)) // not compressed - buffer.writeInteger(UInt32(42)) // message length - buffer.writeRepeatingByte(0, count: 42) // message - let serverDataPayload = HTTP2Frame.FramePayload.Data(data: .byteBuffer(buffer)) - XCTAssertNoThrow(try channel.writeInbound(HTTP2Frame.FramePayload.data(serverDataPayload))) - - // Make sure we read the message properly - XCTAssertEqual( - try channel.readInbound(as: RPCResponsePart.self), - RPCResponsePart.message([UInt8](repeating: 0, count: 42)) - ) - - // Server sends status to end RPC - XCTAssertNoThrow( - try channel.writeInbound( - HTTP2Frame.FramePayload.headers( - .init(headers: [ - GRPCHTTP2Keys.grpcStatus.rawValue: String(Status.Code.dataLoss.rawValue), - GRPCHTTP2Keys.grpcStatusMessage.rawValue: "Test data loss", - "custom-header": "custom-value", - ]) - ) - ) - ) - - XCTAssertEqual( - try channel.readInbound(as: RPCResponsePart.self), - .status(.init(code: .dataLoss, message: "Test data loss"), ["custom-header": "custom-value"]) - ) - } - - func testReceiveMessageSplitAcrossMultipleBuffers() throws { - let handler = GRPCClientStreamHandler( - methodDescriptor: .init(service: "test", method: "test"), - scheme: .http, - outboundEncoding: .none, - acceptedEncodings: [], - maxPayloadSize: 100, - skipStateMachineAssertions: true - ) - - let channel = EmbeddedChannel(handler: handler) - - // Send client's initial metadata - let request = RPCRequestPart.metadata([:]) - XCTAssertNoThrow(try channel.writeOutbound(request)) - - // Make sure we have sent the corresponding frame, and that nothing has been written back. - let writtenHeaders = try channel.assertReadHeadersOutbound() - XCTAssertEqual( - writtenHeaders.headers, - [ - GRPCHTTP2Keys.method.rawValue: "POST", - GRPCHTTP2Keys.scheme.rawValue: "http", - GRPCHTTP2Keys.path.rawValue: "/test/test", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - GRPCHTTP2Keys.te.rawValue: "trailers", - - ] - ) - XCTAssertNil(try channel.readInbound(as: RPCResponsePart.self)) - - // Receive server's initial metadata - let serverInitialMetadata: HPACKHeaders = [ - GRPCHTTP2Keys.status.rawValue: "200", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - "some-custom-header": "some-custom-value", - ] - XCTAssertNoThrow( - try channel.writeInbound( - HTTP2Frame.FramePayload.headers(.init(headers: serverInitialMetadata)) - ) - ) - XCTAssertEqual( - try channel.readInbound(as: RPCResponsePart.self), - RPCResponsePart.metadata(Metadata(headers: serverInitialMetadata)) - ) - - // Send a message - XCTAssertNoThrow( - try channel.writeOutbound(RPCRequestPart.message(.init(repeating: 1, count: 42))) - ) - - // Assert we wrote it successfully into the channel - let writtenMessage = try channel.assertReadDataOutbound() - var expectedBuffer = ByteBuffer() - expectedBuffer.writeInteger(UInt8(0)) // not compressed - expectedBuffer.writeInteger(UInt32(42)) // message length - expectedBuffer.writeRepeatingByte(1, count: 42) // message - XCTAssertEqual(writtenMessage.data, .byteBuffer(expectedBuffer)) - - // Receive server's first message - var buffer = ByteBuffer() - buffer.writeInteger(UInt8(0)) // not compressed - XCTAssertNoThrow( - try channel.writeInbound(HTTP2Frame.FramePayload.data(.init(data: .byteBuffer(buffer)))) - ) - XCTAssertNil(try channel.readInbound(as: RPCResponsePart.self)) - - buffer.clear() - buffer.writeInteger(UInt32(30)) // message length - XCTAssertNoThrow( - try channel.writeInbound(HTTP2Frame.FramePayload.data(.init(data: .byteBuffer(buffer)))) - ) - XCTAssertNil(try channel.readInbound(as: RPCResponsePart.self)) - - buffer.clear() - buffer.writeRepeatingByte(0, count: 10) // first part of the message - XCTAssertNoThrow( - try channel.writeInbound(HTTP2Frame.FramePayload.data(.init(data: .byteBuffer(buffer)))) - ) - XCTAssertNil(try channel.readInbound(as: RPCResponsePart.self)) - - buffer.clear() - buffer.writeRepeatingByte(1, count: 10) // second part of the message - XCTAssertNoThrow( - try channel.writeInbound(HTTP2Frame.FramePayload.data(.init(data: .byteBuffer(buffer)))) - ) - XCTAssertNil(try channel.readInbound(as: RPCResponsePart.self)) - - buffer.clear() - buffer.writeRepeatingByte(2, count: 10) // third part of the message - XCTAssertNoThrow( - try channel.writeInbound(HTTP2Frame.FramePayload.data(.init(data: .byteBuffer(buffer)))) - ) - - // Make sure we read the message properly - XCTAssertEqual( - try channel.readInbound(as: RPCResponsePart.self), - RPCResponsePart.message( - [UInt8](repeating: 0, count: 10) + [UInt8](repeating: 1, count: 10) - + [UInt8](repeating: 2, count: 10) - ) - ) - } - - func testSendMultipleMessagesInSingleBuffer() throws { - let handler = GRPCClientStreamHandler( - methodDescriptor: .init(service: "test", method: "test"), - scheme: .http, - outboundEncoding: .none, - acceptedEncodings: [], - maxPayloadSize: 100, - skipStateMachineAssertions: true - ) - - let channel = EmbeddedChannel(handler: handler) - - // Send client's initial metadata - let request = RPCRequestPart.metadata([:]) - XCTAssertNoThrow(try channel.writeOutbound(request)) - - // Make sure we have sent the corresponding frame, and that nothing has been written back. - let writtenHeaders = try channel.assertReadHeadersOutbound() - XCTAssertEqual( - writtenHeaders.headers, - [ - GRPCHTTP2Keys.method.rawValue: "POST", - GRPCHTTP2Keys.scheme.rawValue: "http", - GRPCHTTP2Keys.path.rawValue: "/test/test", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - GRPCHTTP2Keys.te.rawValue: "trailers", - - ] - ) - XCTAssertNil(try channel.readInbound(as: RPCResponsePart.self)) - - // Receive server's initial metadata - let serverInitialMetadata: HPACKHeaders = [ - GRPCHTTP2Keys.status.rawValue: "200", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - "some-custom-header": "some-custom-value", - ] - XCTAssertNoThrow( - try channel.writeInbound( - HTTP2Frame.FramePayload.headers(.init(headers: serverInitialMetadata)) - ) - ) - XCTAssertEqual( - try channel.readInbound(as: RPCResponsePart.self), - RPCResponsePart.metadata(Metadata(headers: serverInitialMetadata)) - ) - - // This is where this test actually begins. We want to write two messages - // without flushing, and make sure that no messages are sent down the pipeline - // until we flush. Once we flush, both messages should be sent in the same ByteBuffer. - - // Write back first message and make sure nothing's written in the channel. - XCTAssertNoThrow(channel.write(RPCRequestPart.message([UInt8](repeating: 1, count: 4)))) - XCTAssertNil(try channel.readOutbound(as: HTTP2Frame.FramePayload.self)) - - // Write back second message and make sure nothing's written in the channel. - XCTAssertNoThrow(channel.write(RPCRequestPart.message([UInt8](repeating: 2, count: 4)))) - XCTAssertNil(try channel.readOutbound(as: HTTP2Frame.FramePayload.self)) - - // Now flush and check we *do* write the data. - channel.flush() - - let writtenMessage = try channel.assertReadDataOutbound() - - // Make sure both messages have been framed together in the ByteBuffer. - XCTAssertEqual( - writtenMessage.data, - .byteBuffer( - .init(bytes: [ - // First message - 0, // Compression disabled - 0, 0, 0, 4, // Message length - 1, 1, 1, 1, // First message data - - // Second message - 0, // Compression disabled - 0, 0, 0, 4, // Message length - 2, 2, 2, 2, // Second message data - ]) - ) - ) - XCTAssertNil(try channel.readOutbound(as: HTTP2Frame.FramePayload.self)) - } - - func testUnexpectedStreamClose_ErrorFired() throws { - let handler = GRPCClientStreamHandler( - methodDescriptor: .init(service: "test", method: "test"), - scheme: .http, - outboundEncoding: .none, - acceptedEncodings: [], - maxPayloadSize: 1, - skipStateMachineAssertions: true - ) - - let channel = EmbeddedChannel(handler: handler) - - // Write client's initial metadata - XCTAssertNoThrow(try channel.writeOutbound(RPCRequestPart.metadata(Metadata()))) - let clientInitialMetadata: HPACKHeaders = [ - GRPCHTTP2Keys.path.rawValue: "/test/test", - GRPCHTTP2Keys.scheme.rawValue: "http", - GRPCHTTP2Keys.method.rawValue: "POST", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - GRPCHTTP2Keys.te.rawValue: "trailers", - ] - let writtenInitialMetadata = try channel.assertReadHeadersOutbound() - XCTAssertEqual(writtenInitialMetadata.headers, clientInitialMetadata) - - // An error is fired down the pipeline - let thrownError = ChannelError.connectTimeout(.milliseconds(100)) - channel.pipeline.fireErrorCaught(thrownError) - - // The client receives a status explaining the stream was closed because of the thrown error. - XCTAssertEqual( - try channel.readInbound(as: RPCResponsePart.self), - .status( - .init( - code: .unavailable, - message: "Stream unexpectedly closed with error." - ), - [:] - ) - ) - - // We should now be closed: check we can't write anymore. - XCTAssertThrowsError( - ofType: RPCError.self, - try channel.writeOutbound(RPCRequestPart.metadata(Metadata())) - ) { error in - XCTAssertEqual(error.code, .internalError) - XCTAssertEqual(error.message, "Invalid state") - } - } - - func testUnexpectedStreamClose_ChannelInactive() throws { - let handler = GRPCClientStreamHandler( - methodDescriptor: .init(service: "test", method: "test"), - scheme: .http, - outboundEncoding: .none, - acceptedEncodings: [], - maxPayloadSize: 1, - skipStateMachineAssertions: true - ) - - let channel = EmbeddedChannel(handler: handler) - - // Write client's initial metadata - XCTAssertNoThrow(try channel.writeOutbound(RPCRequestPart.metadata(Metadata()))) - let clientInitialMetadata: HPACKHeaders = [ - GRPCHTTP2Keys.path.rawValue: "/test/test", - GRPCHTTP2Keys.scheme.rawValue: "http", - GRPCHTTP2Keys.method.rawValue: "POST", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - GRPCHTTP2Keys.te.rawValue: "trailers", - ] - let writtenInitialMetadata = try channel.assertReadHeadersOutbound() - XCTAssertEqual(writtenInitialMetadata.headers, clientInitialMetadata) - - // Channel becomes inactive - channel.pipeline.fireChannelInactive() - - // The client receives a status explaining the stream was closed. - XCTAssertEqual( - try channel.readInbound(as: RPCResponsePart.self), - .status( - .init(code: .unavailable, message: "Stream unexpectedly closed."), - [:] - ) - ) - - // We should now be closed: check we can't write anymore. - XCTAssertThrowsError( - ofType: RPCError.self, - try channel.writeOutbound(RPCRequestPart.metadata(Metadata())) - ) { error in - XCTAssertEqual(error.code, .internalError) - XCTAssertEqual(error.message, "Invalid state") - } - } - - func testUnexpectedStreamClose_ResetStreamFrame() throws { - let handler = GRPCClientStreamHandler( - methodDescriptor: .init(service: "test", method: "test"), - scheme: .http, - outboundEncoding: .none, - acceptedEncodings: [], - maxPayloadSize: 1, - skipStateMachineAssertions: true - ) - - let channel = EmbeddedChannel(handler: handler) - - // Write client's initial metadata - XCTAssertNoThrow(try channel.writeOutbound(RPCRequestPart.metadata(Metadata()))) - let clientInitialMetadata: HPACKHeaders = [ - GRPCHTTP2Keys.path.rawValue: "/test/test", - GRPCHTTP2Keys.scheme.rawValue: "http", - GRPCHTTP2Keys.method.rawValue: "POST", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - GRPCHTTP2Keys.te.rawValue: "trailers", - ] - let writtenInitialMetadata = try channel.assertReadHeadersOutbound() - XCTAssertEqual(writtenInitialMetadata.headers, clientInitialMetadata) - - // Receive RST_STREAM - XCTAssertNoThrow( - try channel.writeInbound( - HTTP2Frame.FramePayload.rstStream(.internalError) - ) - ) - - // The client receives a status explaining RST_STREAM was sent. - XCTAssertEqual( - try channel.readInbound(as: RPCResponsePart.self), - .status( - .init( - code: .unavailable, - message: "Stream unexpectedly closed: a RST_STREAM frame was received." - ), - [:] - ) - ) - - // We should now be closed: check we can't write anymore. - XCTAssertThrowsError( - ofType: RPCError.self, - try channel.writeOutbound(RPCRequestPart.metadata(Metadata())) - ) { error in - XCTAssertEqual(error.code, .internalError) - XCTAssertEqual(error.message, "Invalid state") - } - } -} - -extension EmbeddedChannel { - fileprivate func assertReadHeadersOutbound() throws -> HTTP2Frame.FramePayload.Headers { - guard - case .headers(let writtenHeaders) = try XCTUnwrap( - try self.readOutbound(as: HTTP2Frame.FramePayload.self) - ) - else { - throw TestError.assertionFailure("Expected to write headers") - } - return writtenHeaders - } - - fileprivate func assertReadDataOutbound() throws -> HTTP2Frame.FramePayload.Data { - guard - case .data(let writtenMessage) = try XCTUnwrap( - try self.readOutbound(as: HTTP2Frame.FramePayload.self) - ) - else { - throw TestError.assertionFailure("Expected to write data") - } - return writtenMessage - } -} - -private enum TestError: Error { - case assertionFailure(String) -} diff --git a/Tests/GRPCHTTP2CoreTests/Client/HTTP2ClientTransportConfigTests.swift b/Tests/GRPCHTTP2CoreTests/Client/HTTP2ClientTransportConfigTests.swift deleted file mode 100644 index 8b5857008..000000000 --- a/Tests/GRPCHTTP2CoreTests/Client/HTTP2ClientTransportConfigTests.swift +++ /dev/null @@ -1,47 +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 GRPCHTTP2Core -import XCTest - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -final class HTTP2ClientTransportConfigTests: XCTestCase { - func testCompressionDefaults() { - let config = HTTP2ClientTransport.Config.Compression.defaults - XCTAssertEqual(config.algorithm, .none) - XCTAssertEqual(config.enabledAlgorithms, .none) - } - - func testConnectionDefaults() { - let config = HTTP2ClientTransport.Config.Connection.defaults - XCTAssertEqual(config.maxIdleTime, .seconds(30 * 60)) - XCTAssertNil(config.keepalive) - } - - func testBackoffDefaults() { - let config = HTTP2ClientTransport.Config.Backoff.defaults - XCTAssertEqual(config.initial, .seconds(1)) - XCTAssertEqual(config.max, .seconds(120)) - XCTAssertEqual(config.multiplier, 1.6) - XCTAssertEqual(config.jitter, 0.2) - } - - func testHTTP2Defaults() { - let config = HTTP2ClientTransport.Config.HTTP2.defaults - XCTAssertEqual(config.maxFrameSize, 16384) - XCTAssertEqual(config.targetWindowSize, 8 * 1024 * 1024) - } -} diff --git a/Tests/GRPCHTTP2CoreTests/Client/Resolver/NameResolverRegistryTests.swift b/Tests/GRPCHTTP2CoreTests/Client/Resolver/NameResolverRegistryTests.swift deleted file mode 100644 index 82d27a421..000000000 --- a/Tests/GRPCHTTP2CoreTests/Client/Resolver/NameResolverRegistryTests.swift +++ /dev/null @@ -1,271 +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 XCTest - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -final class NameResolverRegistryTests: XCTestCase { - struct FailingResolver: NameResolverFactory { - typealias Target = StringTarget - - private let code: RPCError.Code - - init(code: RPCError.Code = .unavailable) { - self.code = code - } - - func resolver(for target: NameResolverRegistryTests.StringTarget) -> NameResolver { - let stream = AsyncThrowingStream(NameResolutionResult.self) { - $0.yield(with: .failure(RPCError(code: self.code, message: target.value))) - } - - return NameResolver(names: RPCAsyncSequence(wrapping: stream), updateMode: .pull) - } - } - - struct StringTarget: ResolvableTarget { - var value: String - - init(value: String) { - self.value = value - } - } - - func testEmptyNameResolvers() { - let resolvers = NameResolverRegistry() - XCTAssert(resolvers.isEmpty) - XCTAssertEqual(resolvers.count, 0) - } - - func testRegisterFactory() async throws { - var resolvers = NameResolverRegistry() - resolvers.registerFactory(FailingResolver(code: .unknown)) - XCTAssertEqual(resolvers.count, 1) - - do { - let resolver = resolvers.makeResolver(for: StringTarget(value: "foo")) - await XCTAssertThrowsErrorAsync(ofType: RPCError.self) { - var iterator = resolver?.names.makeAsyncIterator() - _ = try await iterator?.next() - } errorHandler: { error in - XCTAssertEqual(error.code, .unknown) - } - } - - // Adding a resolver of the same type replaces it. Use the code of the thrown error to - // distinguish between the instances. - resolvers.registerFactory(FailingResolver(code: .cancelled)) - XCTAssertEqual(resolvers.count, 1) - - do { - let resolver = resolvers.makeResolver(for: StringTarget(value: "foo")) - await XCTAssertThrowsErrorAsync(ofType: RPCError.self) { - var iterator = resolver?.names.makeAsyncIterator() - _ = try await iterator?.next() - } errorHandler: { error in - XCTAssertEqual(error.code, .cancelled) - } - } - } - - func testRemoveFactory() { - var resolvers = NameResolverRegistry() - resolvers.registerFactory(FailingResolver()) - XCTAssertEqual(resolvers.count, 1) - - resolvers.removeFactory(ofType: FailingResolver.self) - XCTAssertEqual(resolvers.count, 0) - - // Removing an unknown factory is a no-op. - resolvers.removeFactory(ofType: FailingResolver.self) - XCTAssertEqual(resolvers.count, 0) - } - - func testContainsFactoryOfType() { - var resolvers = NameResolverRegistry() - XCTAssertFalse(resolvers.containsFactory(ofType: FailingResolver.self)) - - resolvers.registerFactory(FailingResolver()) - XCTAssertTrue(resolvers.containsFactory(ofType: FailingResolver.self)) - } - - func testContainsFactoryCapableOfResolving() { - var resolvers = NameResolverRegistry() - XCTAssertFalse(resolvers.containsFactory(capableOfResolving: StringTarget(value: ""))) - - resolvers.registerFactory(FailingResolver()) - XCTAssertTrue(resolvers.containsFactory(capableOfResolving: StringTarget(value: ""))) - } - - func testMakeFailingResolver() async throws { - var resolvers = NameResolverRegistry() - XCTAssertNil(resolvers.makeResolver(for: StringTarget(value: ""))) - - resolvers.registerFactory(FailingResolver()) - - let resolver = try XCTUnwrap(resolvers.makeResolver(for: StringTarget(value: "foo"))) - XCTAssertEqual(resolver.updateMode, .pull) - - var iterator = resolver.names.makeAsyncIterator() - await XCTAssertThrowsErrorAsync(ofType: RPCError.self) { - try await iterator.next() - } errorHandler: { error in - XCTAssertEqual(error.code, .unavailable) - XCTAssertEqual(error.message, "foo") - } - } - - func testDefaultResolvers() { - let resolvers = NameResolverRegistry.defaults - XCTAssert(resolvers.containsFactory(ofType: NameResolvers.IPv4.self)) - XCTAssert(resolvers.containsFactory(ofType: NameResolvers.IPv6.self)) - XCTAssert(resolvers.containsFactory(ofType: NameResolvers.UnixDomainSocket.self)) - XCTAssert(resolvers.containsFactory(ofType: NameResolvers.VirtualSocket.self)) - XCTAssertEqual(resolvers.count, 4) - } - - func testMakeResolver() { - let resolvers = NameResolverRegistry() - XCTAssertNil(resolvers.makeResolver(for: .ipv4(host: "foo"))) - } - - func testCustomResolver() async throws { - struct EmptyTarget: ResolvableTarget { - static var scheme: String { "empty" } - } - - struct CustomResolver: NameResolverFactory { - func resolver(for target: EmptyTarget) -> NameResolver { - return NameResolver( - names: RPCAsyncSequence(wrapping: AsyncThrowingStream { $0.finish() }), - updateMode: .push - ) - } - } - - var resolvers = NameResolverRegistry.defaults - resolvers.registerFactory(CustomResolver()) - let resolver = try XCTUnwrap(resolvers.makeResolver(for: EmptyTarget())) - XCTAssertEqual(resolver.updateMode, .push) - for try await _ in resolver.names { - XCTFail("Expected an empty sequence") - } - } - - func testIPv4ResolverForSingleHost() async throws { - let factory = NameResolvers.IPv4() - let resolver = factory.resolver(for: .ipv4(host: "foo", port: 1234)) - - XCTAssertEqual(resolver.updateMode, .pull) - - // The IPv4 resolver always returns the same values. - var iterator = resolver.names.makeAsyncIterator() - for _ in 0 ..< 1000 { - let result = try await XCTUnwrapAsync { try await iterator.next() } - XCTAssertEqual(result.endpoints, [Endpoint(addresses: [.ipv4(host: "foo", port: 1234)])]) - XCTAssertNil(result.serviceConfig) - } - } - - func testIPv4ResolverForMultipleHosts() async throws { - let factory = NameResolvers.IPv4() - let resolver = factory.resolver(for: .ipv4(pairs: [("foo", 443), ("bar", 444)])) - - XCTAssertEqual(resolver.updateMode, .pull) - - // The IPv4 resolver always returns the same values. - var iterator = resolver.names.makeAsyncIterator() - for _ in 0 ..< 1000 { - let result = try await XCTUnwrapAsync { try await iterator.next() } - XCTAssertEqual( - result.endpoints, - [ - Endpoint(addresses: [.ipv4(host: "foo", port: 443)]), - Endpoint(addresses: [.ipv4(host: "bar", port: 444)]), - ] - ) - XCTAssertNil(result.serviceConfig) - } - } - - func testIPv6ResolverForSingleHost() async throws { - let factory = NameResolvers.IPv6() - let resolver = factory.resolver(for: .ipv6(host: "foo", port: 1234)) - - XCTAssertEqual(resolver.updateMode, .pull) - - // The IPv6 resolver always returns the same values. - var iterator = resolver.names.makeAsyncIterator() - for _ in 0 ..< 1000 { - let result = try await XCTUnwrapAsync { try await iterator.next() } - XCTAssertEqual(result.endpoints, [Endpoint(addresses: [.ipv6(host: "foo", port: 1234)])]) - XCTAssertNil(result.serviceConfig) - } - } - - func testIPv6ResolverForMultipleHosts() async throws { - let factory = NameResolvers.IPv6() - let resolver = factory.resolver(for: .ipv6(pairs: [("foo", 443), ("bar", 444)])) - - XCTAssertEqual(resolver.updateMode, .pull) - - // The IPv6 resolver always returns the same values. - var iterator = resolver.names.makeAsyncIterator() - for _ in 0 ..< 1000 { - let result = try await XCTUnwrapAsync { try await iterator.next() } - XCTAssertEqual( - result.endpoints, - [ - Endpoint(addresses: [.ipv6(host: "foo", port: 443)]), - Endpoint(addresses: [.ipv6(host: "bar", port: 444)]), - ] - ) - XCTAssertNil(result.serviceConfig) - } - } - - func testUDSResolver() async throws { - let factory = NameResolvers.UnixDomainSocket() - let resolver = factory.resolver(for: .unixDomainSocket(path: "/foo")) - - XCTAssertEqual(resolver.updateMode, .pull) - - // The UDS resolver always returns the same values. - var iterator = resolver.names.makeAsyncIterator() - for _ in 0 ..< 1000 { - let result = try await XCTUnwrapAsync { try await iterator.next() } - XCTAssertEqual(result.endpoints, [Endpoint(addresses: [.unixDomainSocket(path: "/foo")])]) - XCTAssertNil(result.serviceConfig) - } - } - - func testVSOCKResolver() async throws { - let factory = NameResolvers.VirtualSocket() - let resolver = factory.resolver(for: .vsock(contextID: .any, port: .any)) - - XCTAssertEqual(resolver.updateMode, .pull) - - // The VSOCK resolver always returns the same values. - var iterator = resolver.names.makeAsyncIterator() - for _ in 0 ..< 1000 { - let result = try await XCTUnwrapAsync { try await iterator.next() } - XCTAssertEqual(result.endpoints, [Endpoint(addresses: [.vsock(contextID: .any, port: .any)])]) - XCTAssertNil(result.serviceConfig) - } - } -} diff --git a/Tests/GRPCHTTP2CoreTests/Client/Resolver/SocketAddressTests.swift b/Tests/GRPCHTTP2CoreTests/Client/Resolver/SocketAddressTests.swift deleted file mode 100644 index ab081a631..000000000 --- a/Tests/GRPCHTTP2CoreTests/Client/Resolver/SocketAddressTests.swift +++ /dev/null @@ -1,80 +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 GRPCHTTP2Core -import XCTest - -final class SocketAddressTests: XCTestCase { - func testSocketAddressUnwrapping() { - var address: SocketAddress = .ipv4(host: "foo", port: 42) - XCTAssertEqual(address.ipv4, SocketAddress.IPv4(host: "foo", port: 42)) - XCTAssertNil(address.ipv6) - XCTAssertNil(address.unixDomainSocket) - XCTAssertNil(address.virtualSocket) - - address = .ipv6(host: "bar", port: 42) - XCTAssertEqual(address.ipv6, SocketAddress.IPv6(host: "bar", port: 42)) - XCTAssertNil(address.ipv4) - XCTAssertNil(address.unixDomainSocket) - XCTAssertNil(address.virtualSocket) - - address = .unixDomainSocket(path: "baz") - XCTAssertEqual(address.unixDomainSocket, SocketAddress.UnixDomainSocket(path: "baz")) - XCTAssertNil(address.ipv4) - XCTAssertNil(address.ipv6) - XCTAssertNil(address.virtualSocket) - - address = .vsock(contextID: .any, port: .any) - XCTAssertEqual(address.virtualSocket, SocketAddress.VirtualSocket(contextID: .any, port: .any)) - XCTAssertNil(address.ipv4) - XCTAssertNil(address.ipv6) - XCTAssertNil(address.unixDomainSocket) - } - - func testSocketAddressDescription() { - var address: SocketAddress = .ipv4(host: "127.0.0.1", port: 42) - XCTAssertDescription(address, "[ipv4]127.0.0.1:42") - - address = .ipv6(host: "::1", port: 42) - XCTAssertDescription(address, "[ipv6]::1:42") - - address = .unixDomainSocket(path: "baz") - XCTAssertDescription(address, "[unix]baz") - - address = .vsock(contextID: 314, port: 159) - XCTAssertDescription(address, "[vsock]314:159") - address = .vsock(contextID: .any, port: .any) - XCTAssertDescription(address, "[vsock]-1:-1") - - } - - func testSocketAddressSubTypesDescription() { - let ipv4 = SocketAddress.IPv4(host: "127.0.0.1", port: 42) - XCTAssertDescription(ipv4, "[ipv4]127.0.0.1:42") - - let ipv6 = SocketAddress.IPv6(host: "foo", port: 42) - XCTAssertDescription(ipv6, "[ipv6]foo:42") - - let uds = SocketAddress.UnixDomainSocket(path: "baz") - XCTAssertDescription(uds, "[unix]baz") - - var vsock = SocketAddress.VirtualSocket(contextID: 314, port: 159) - XCTAssertDescription(vsock, "[vsock]314:159") - vsock.contextID = .any - vsock.port = .any - XCTAssertDescription(vsock, "[vsock]-1:-1") - } -} diff --git a/Tests/GRPCHTTP2CoreTests/GRPCMessageDecoderTests.swift b/Tests/GRPCHTTP2CoreTests/GRPCMessageDecoderTests.swift deleted file mode 100644 index 544825a84..000000000 --- a/Tests/GRPCHTTP2CoreTests/GRPCMessageDecoderTests.swift +++ /dev/null @@ -1,237 +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 NIOCore -import NIOTestUtils -import XCTest - -@testable import GRPCHTTP2Core - -final class GRPCMessageDecoderTests: XCTestCase { - func testReadMultipleMessagesWithoutCompression() throws { - let firstMessage = { - var buffer = ByteBuffer() - buffer.writeInteger(UInt8(0)) - buffer.writeInteger(UInt32(16)) - buffer.writeRepeatingByte(42, count: 16) - return buffer - }() - - let secondMessage = { - var buffer = ByteBuffer() - buffer.writeInteger(UInt8(0)) - buffer.writeInteger(UInt32(8)) - buffer.writeRepeatingByte(43, count: 8) - return buffer - }() - - try ByteToMessageDecoderVerifier.verifyDecoder( - inputOutputPairs: [ - (firstMessage, [Array(repeating: UInt8(42), count: 16)]), - (secondMessage, [Array(repeating: UInt8(43), count: 8)]), - ]) { - GRPCMessageDecoder(maxPayloadSize: .max) - } - } - - func testReadMessageOverSizeLimitWithoutCompression() throws { - let deframer = GRPCMessageDecoder(maxPayloadSize: 100) - let processor = NIOSingleStepByteToMessageProcessor(deframer) - - var buffer = ByteBuffer() - buffer.writeInteger(UInt8(0)) - buffer.writeInteger(UInt32(101)) - buffer.writeRepeatingByte(42, count: 101) - - XCTAssertThrowsError( - ofType: RPCError.self, - try processor.process(buffer: buffer) { _ in - XCTFail("No message should be produced.") - } - ) { error in - XCTAssertEqual(error.code, .resourceExhausted) - XCTAssertEqual( - error.message, - "Message has exceeded the configured maximum payload size (max: 100, actual: 101)" - ) - } - } - - func testReadMessageOverSizeLimitButWithoutActualMessageBytes() throws { - let deframer = GRPCMessageDecoder(maxPayloadSize: 100) - let processor = NIOSingleStepByteToMessageProcessor(deframer) - - var buffer = ByteBuffer() - buffer.writeInteger(UInt8(0)) - // Set the message length field to be over the maximum payload size, but - // don't write the actual message bytes. This is to ensure that the payload - // size limit is enforced _before_ the payload is actually read. - buffer.writeInteger(UInt32(101)) - - XCTAssertThrowsError( - ofType: RPCError.self, - try processor.process(buffer: buffer) { _ in - XCTFail("No message should be produced.") - } - ) { error in - XCTAssertEqual(error.code, .resourceExhausted) - XCTAssertEqual( - error.message, - "Message has exceeded the configured maximum payload size (max: 100, actual: 101)" - ) - } - } - - func testCompressedMessageWithoutConfiguringDecompressor() throws { - let deframer = GRPCMessageDecoder(maxPayloadSize: 100) - let processor = NIOSingleStepByteToMessageProcessor(deframer) - - var buffer = ByteBuffer() - buffer.writeInteger(UInt8(1)) - buffer.writeInteger(UInt32(10)) - buffer.writeRepeatingByte(42, count: 10) - - XCTAssertThrowsError( - ofType: RPCError.self, - try processor.process(buffer: buffer) { _ in - XCTFail("No message should be produced.") - } - ) { error in - XCTAssertEqual(error.code, .internalError) - XCTAssertEqual( - error.message, - "Received a compressed message payload, but no decompressor has been configured." - ) - } - } - - private func testReadMultipleMessagesWithCompression(method: Zlib.Method) throws { - let decompressor = Zlib.Decompressor(method: method) - let compressor = Zlib.Compressor(method: method) - var framer = GRPCMessageFramer() - defer { - decompressor.end() - compressor.end() - } - - let firstMessage = try { - framer.append(Array(repeating: 42, count: 100), promise: nil) - return try framer.next(compressor: compressor)! - }() - - let secondMessage = try { - framer.append(Array(repeating: 43, count: 110), promise: nil) - return try framer.next(compressor: compressor)! - }() - - try ByteToMessageDecoderVerifier.verifyDecoder( - inputOutputPairs: [ - (firstMessage.bytes, [Array(repeating: 42, count: 100)]), - (secondMessage.bytes, [Array(repeating: 43, count: 110)]), - ]) { - GRPCMessageDecoder(maxPayloadSize: 1000, decompressor: decompressor) - } - } - - func testReadMultipleMessagesWithDeflateCompression() throws { - try self.testReadMultipleMessagesWithCompression(method: .deflate) - } - - func testReadMultipleMessagesWithGZIPCompression() throws { - try self.testReadMultipleMessagesWithCompression(method: .gzip) - } - - func testReadCompressedMessageOverSizeLimitBeforeDecompressing() throws { - let deframer = GRPCMessageDecoder(maxPayloadSize: 1) - let processor = NIOSingleStepByteToMessageProcessor(deframer) - let compressor = Zlib.Compressor(method: .gzip) - var framer = GRPCMessageFramer() - defer { - compressor.end() - } - - framer.append(Array(repeating: 42, count: 100), promise: nil) - let framedMessage = try framer.next(compressor: compressor)! - - XCTAssertThrowsError( - ofType: RPCError.self, - try processor.process(buffer: framedMessage.bytes) { _ in - XCTFail("No message should be produced.") - } - ) { error in - XCTAssertEqual(error.code, .resourceExhausted) - XCTAssertEqual( - error.message, - """ - Message has exceeded the configured maximum payload size \ - (max: 1, actual: \(framedMessage.bytes.readableBytes - GRPCMessageDecoder.metadataLength)) - """ - ) - } - } - - private func testReadDecompressedMessageOverSizeLimit(method: Zlib.Method) throws { - let decompressor = Zlib.Decompressor(method: method) - let deframer = GRPCMessageDecoder(maxPayloadSize: 100, decompressor: decompressor) - let processor = NIOSingleStepByteToMessageProcessor(deframer) - let compressor = Zlib.Compressor(method: method) - var framer = GRPCMessageFramer() - defer { - decompressor.end() - compressor.end() - } - - framer.append(Array(repeating: 42, count: 101), promise: nil) - let framedMessage = try framer.next(compressor: compressor)! - - XCTAssertThrowsError( - ofType: RPCError.self, - try processor.process(buffer: framedMessage.bytes) { _ in - XCTFail("No message should be produced.") - } - ) { error in - XCTAssertEqual(error.code, .resourceExhausted) - XCTAssertEqual(error.message, "Message is too large to decompress.") - } - } - - func testReadDecompressedMessageOverSizeLimitWithDeflateCompression() throws { - try self.testReadDecompressedMessageOverSizeLimit(method: .deflate) - } - - func testReadDecompressedMessageOverSizeLimitWithGZIPCompression() throws { - try self.testReadDecompressedMessageOverSizeLimit(method: .gzip) - } -} - -extension GRPCMessageFramer { - mutating func next( - compressor: Zlib.Compressor? = nil - ) throws(RPCError) -> (bytes: ByteBuffer, promise: EventLoopPromise?)? { - if let (result, promise) = self.nextResult(compressor: compressor) { - switch result { - case .success(let buffer): - return (bytes: buffer, promise: promise) - case .failure(let error): - promise?.fail(error) - throw error - } - } else { - return nil - } - } -} diff --git a/Tests/GRPCHTTP2CoreTests/GRPCMessageDeframerTests.swift b/Tests/GRPCHTTP2CoreTests/GRPCMessageDeframerTests.swift deleted file mode 100644 index a5ef3d8b4..000000000 --- a/Tests/GRPCHTTP2CoreTests/GRPCMessageDeframerTests.swift +++ /dev/null @@ -1,106 +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 GRPCHTTP2Core -import NIOCore -import XCTest - -final class GRPCMessageDeframerTests: XCTestCase { - // Most of the functionality is tested by the 'GRPCMessageDecoder' tests. - - func testDecodeNoBytes() { - var deframer = GRPCMessageDeframer(maxPayloadSize: .max) - XCTAssertNil(try deframer.decodeNext()) - } - - func testDecodeNotEnoughBytes() { - var deframer = GRPCMessageDeframer(maxPayloadSize: .max) - let bytes: [UInt8] = [ - 0x0, // Compression byte (not compressed) - 0x0, 0x0, 0x0, 0x1, // Length (1) - ] - deframer.append(ByteBuffer(bytes: bytes)) - XCTAssertNil(try deframer.decodeNext()) - } - - func testDecodeZeroLengthMessage() { - var deframer = GRPCMessageDeframer(maxPayloadSize: .max) - let bytes: [UInt8] = [ - 0x0, // Compression byte (not compressed) - 0x0, 0x0, 0x0, 0x0, // Length (0) - ] - deframer.append(ByteBuffer(bytes: bytes)) - XCTAssertEqual(try deframer.decodeNext(), []) - } - - func testDecodeMessage() { - var deframer = GRPCMessageDeframer(maxPayloadSize: .max) - let bytes: [UInt8] = [ - 0x0, // Compression byte (not compressed) - 0x0, 0x0, 0x0, 0x1, // Length (1) - 0xf, // Payload - ] - deframer.append(ByteBuffer(bytes: bytes)) - XCTAssertEqual(try deframer.decodeNext(), [0xf]) - } - - func testDripFeedAndDecode() { - var deframer = GRPCMessageDeframer(maxPayloadSize: .max) - let bytes: [UInt8] = [ - 0x0, // Compression byte (not compressed) - 0x0, 0x0, 0x0, 0x1, // Length (1) - ] - - for byte in bytes { - deframer.append(ByteBuffer(bytes: [byte])) - XCTAssertNil(try deframer.decodeNext()) - } - - // Drip feed the last byte. - deframer.append(ByteBuffer(bytes: [0xf])) - XCTAssertEqual(try deframer.decodeNext(), [0xf]) - } - - func testReadBytesAreDiscarded() throws { - var deframer = GRPCMessageDeframer(maxPayloadSize: .max) - - var input = ByteBuffer() - input.writeInteger(UInt8(0)) // Compression byte (not compressed) - input.writeInteger(UInt32(1024)) // Length - input.writeRepeatingByte(42, count: 1024) // Payload - - input.writeInteger(UInt8(0)) // Compression byte (not compressed) - input.writeInteger(UInt32(1024)) // Length - input.writeRepeatingByte(43, count: 512) // Payload (most of it) - - deframer.append(input) - XCTAssertEqual(deframer._readerIndex, 0) - - let message1 = try deframer.decodeNext() - XCTAssertEqual(message1, Array(repeating: 42, count: 1024)) - XCTAssertNotEqual(deframer._readerIndex, 0) - - // Append the final byte. This should discard any read bytes and set the reader index back - // to zero. - deframer.append(ByteBuffer(repeating: 43, count: 512)) - XCTAssertEqual(deframer._readerIndex, 0) - - // Read the message - let message2 = try deframer.decodeNext() - XCTAssertEqual(message2, Array(repeating: 43, count: 1024)) - XCTAssertNotEqual(deframer._readerIndex, 0) - } -} diff --git a/Tests/GRPCHTTP2CoreTests/GRPCMessageFramerTests.swift b/Tests/GRPCHTTP2CoreTests/GRPCMessageFramerTests.swift deleted file mode 100644 index bf9696e73..000000000 --- a/Tests/GRPCHTTP2CoreTests/GRPCMessageFramerTests.swift +++ /dev/null @@ -1,129 +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 NIOCore -import NIOEmbedded -import XCTest - -@testable import GRPCHTTP2Core - -final class GRPCMessageFramerTests: XCTestCase { - func testSingleWrite() throws { - var framer = GRPCMessageFramer() - framer.append(Array(repeating: 42, count: 128), promise: nil) - - var buffer = try XCTUnwrap(framer.next()).bytes - let (compressed, length) = try XCTUnwrap(buffer.readMessageHeader()) - XCTAssertFalse(compressed) - XCTAssertEqual(length, 128) - XCTAssertEqual(buffer.readSlice(length: Int(length)), ByteBuffer(repeating: 42, count: 128)) - XCTAssertEqual(buffer.readableBytes, 0) - - // No more bufers. - XCTAssertNil(try framer.next()) - } - - private func testSingleWrite(compressionMethod: Zlib.Method) throws { - let compressor = Zlib.Compressor(method: compressionMethod) - defer { - compressor.end() - } - var framer = GRPCMessageFramer() - - let message = [UInt8](repeating: 42, count: 128) - framer.append(message, promise: nil) - - var buffer = ByteBuffer() - let testCompressor = Zlib.Compressor(method: compressionMethod) - let compressedSize = try testCompressor.compress(message, into: &buffer) - let compressedMessage = buffer.readSlice(length: compressedSize) - defer { - testCompressor.end() - } - - buffer = try XCTUnwrap(framer.next(compressor: compressor)).bytes - let (compressed, length) = try XCTUnwrap(buffer.readMessageHeader()) - XCTAssertTrue(compressed) - XCTAssertEqual(length, UInt32(compressedSize)) - XCTAssertEqual(buffer.readSlice(length: Int(length)), compressedMessage) - XCTAssertEqual(buffer.readableBytes, 0) - - // No more bufers. - XCTAssertNil(try framer.next()) - } - - func testSingleWriteDeflateCompressed() throws { - try self.testSingleWrite(compressionMethod: .deflate) - } - - func testSingleWriteGZIPCompressed() throws { - try self.testSingleWrite(compressionMethod: .gzip) - } - - func testMultipleWrites() throws { - var framer = GRPCMessageFramer() - let eventLoop = EmbeddedEventLoop() - - // Create 100 messages and link a different promise with each of them. - let messagesCount = 100 - var promises = [EventLoopPromise]() - promises.reserveCapacity(messagesCount) - for _ in 0 ..< messagesCount { - let promise = eventLoop.makePromise(of: Void.self) - promises.append(promise) - framer.append(Array(repeating: 42, count: 128), promise: promise) - } - - let nextFrame = try XCTUnwrap(framer.next()) - - // Assert the messages have been framed all together in the same frame. - var buffer = nextFrame.bytes - for _ in 0 ..< messagesCount { - let (compressed, length) = try XCTUnwrap(buffer.readMessageHeader()) - XCTAssertFalse(compressed) - XCTAssertEqual(length, 128) - XCTAssertEqual(buffer.readSlice(length: Int(length)), ByteBuffer(repeating: 42, count: 128)) - } - XCTAssertEqual(buffer.readableBytes, 0) - - // Assert the promise returned from the framer is the promise linked to the - // first message appended to the framer. - let returnedPromise = nextFrame.promise - XCTAssertEqual(returnedPromise?.futureResult, promises.first?.futureResult) - - // Succeed the returned promise to simulate a write into the channel - // succeeding, and assert that all other promises have been chained and are - // also succeeded as a result. - returnedPromise?.succeed() - XCTAssertEqual(promises.count, messagesCount) - for promise in promises { - try promise.futureResult.assertSuccess().wait() - } - - // No more frames. - XCTAssertNil(try framer.next()) - } -} - -extension ByteBuffer { - mutating func readMessageHeader() -> (Bool, UInt32)? { - if let (compressed, length) = self.readMultipleIntegers(as: (UInt8, UInt32).self) { - return (compressed != 0, length) - } else { - return nil - } - } -} diff --git a/Tests/GRPCHTTP2CoreTests/GRPCStreamStateMachineTests.swift b/Tests/GRPCHTTP2CoreTests/GRPCStreamStateMachineTests.swift deleted file mode 100644 index 4ca3e608b..000000000 --- a/Tests/GRPCHTTP2CoreTests/GRPCStreamStateMachineTests.swift +++ /dev/null @@ -1,2864 +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 NIOCore -import NIOEmbedded -import NIOHPACK -import XCTest - -@testable import GRPCHTTP2Core - -private enum TargetStateMachineState: CaseIterable { - case clientIdleServerIdle - case clientOpenServerIdle - case clientOpenServerOpen - case clientOpenServerClosed - case clientClosedServerIdle - case clientClosedServerOpen - case clientClosedServerClosed -} - -extension HPACKHeaders { - // Client - fileprivate static let clientInitialMetadata: Self = [ - GRPCHTTP2Keys.path.rawValue: "/test/test", - GRPCHTTP2Keys.scheme.rawValue: "http", - GRPCHTTP2Keys.method.rawValue: "POST", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - GRPCHTTP2Keys.te.rawValue: "trailers", - ] - fileprivate static let clientInitialMetadataWithDeflateCompression: Self = [ - GRPCHTTP2Keys.path.rawValue: "/test/test", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - GRPCHTTP2Keys.method.rawValue: "POST", - GRPCHTTP2Keys.scheme.rawValue: "https", - GRPCHTTP2Keys.te.rawValue: "trailers", - GRPCHTTP2Keys.acceptEncoding.rawValue: "deflate", - GRPCHTTP2Keys.encoding.rawValue: "deflate", - ] - fileprivate static let clientInitialMetadataWithGzipCompression: Self = [ - GRPCHTTP2Keys.path.rawValue: "/test/test", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - GRPCHTTP2Keys.method.rawValue: "POST", - GRPCHTTP2Keys.scheme.rawValue: "https", - GRPCHTTP2Keys.te.rawValue: "trailers", - GRPCHTTP2Keys.acceptEncoding.rawValue: "gzip", - GRPCHTTP2Keys.encoding.rawValue: "gzip", - ] - fileprivate static let receivedWithoutContentType: Self = [ - GRPCHTTP2Keys.path.rawValue: "/test/test" - ] - fileprivate static let receivedWithInvalidContentType: Self = [ - GRPCHTTP2Keys.path.rawValue: "/test/test", - GRPCHTTP2Keys.contentType.rawValue: "invalid/invalid", - ] - fileprivate static let receivedWithInvalidPath: Self = [ - GRPCHTTP2Keys.path.rawValue: "someinvalidpath", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - ] - fileprivate static let receivedWithoutEndpoint: Self = [ - GRPCHTTP2Keys.contentType.rawValue: "application/grpc" - ] - fileprivate static let receivedWithoutTE: Self = [ - GRPCHTTP2Keys.path.rawValue: "/test/test", - GRPCHTTP2Keys.scheme.rawValue: "http", - GRPCHTTP2Keys.method.rawValue: "POST", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - ] - fileprivate static let receivedWithInvalidTE: Self = [ - GRPCHTTP2Keys.path.rawValue: "/test/test", - GRPCHTTP2Keys.scheme.rawValue: "http", - GRPCHTTP2Keys.method.rawValue: "POST", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - GRPCHTTP2Keys.te.rawValue: "invalidte", - ] - fileprivate static let receivedWithoutMethod: Self = [ - GRPCHTTP2Keys.path.rawValue: "/test/test", - GRPCHTTP2Keys.scheme.rawValue: "http", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - GRPCHTTP2Keys.te.rawValue: "trailers", - ] - fileprivate static let receivedWithInvalidMethod: Self = [ - GRPCHTTP2Keys.path.rawValue: "/test/test", - GRPCHTTP2Keys.scheme.rawValue: "http", - GRPCHTTP2Keys.method.rawValue: "GET", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - GRPCHTTP2Keys.te.rawValue: "trailers", - ] - fileprivate static let receivedWithoutScheme: Self = [ - GRPCHTTP2Keys.path.rawValue: "/test/test", - GRPCHTTP2Keys.method.rawValue: "POST", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - GRPCHTTP2Keys.te.rawValue: "trailers", - ] - fileprivate static let receivedWithInvalidScheme: Self = [ - GRPCHTTP2Keys.path.rawValue: "/test/test", - GRPCHTTP2Keys.scheme.rawValue: "invalidscheme", - GRPCHTTP2Keys.method.rawValue: "POST", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - GRPCHTTP2Keys.te.rawValue: "trailers", - ] - - // Server - fileprivate static let serverInitialMetadata: Self = [ - GRPCHTTP2Keys.status.rawValue: "200", - GRPCHTTP2Keys.contentType.rawValue: ContentType.grpc.canonicalValue, - ] - fileprivate static let serverInitialMetadataWithDeflateCompression: Self = [ - GRPCHTTP2Keys.status.rawValue: "200", - GRPCHTTP2Keys.contentType.rawValue: ContentType.grpc.canonicalValue, - GRPCHTTP2Keys.encoding.rawValue: "deflate", - ] - fileprivate static let serverInitialMetadataWithGZIPCompression: Self = [ - GRPCHTTP2Keys.status.rawValue: "200", - GRPCHTTP2Keys.contentType.rawValue: ContentType.grpc.canonicalValue, - GRPCHTTP2Keys.encoding.rawValue: "gzip", - ] - fileprivate static let serverTrailers: Self = [ - GRPCHTTP2Keys.status.rawValue: "200", - GRPCHTTP2Keys.contentType.rawValue: ContentType.grpc.canonicalValue, - GRPCHTTP2Keys.grpcStatus.rawValue: "0", - ] -} - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -final class GRPCStreamClientStateMachineTests: XCTestCase { - private func makeClientStateMachine( - targetState: TargetStateMachineState, - compressionEnabled: Bool = false - ) -> GRPCStreamStateMachine { - var stateMachine = GRPCStreamStateMachine( - configuration: .client( - .init( - methodDescriptor: .init(service: "test", method: "test"), - scheme: .http, - outboundEncoding: compressionEnabled ? .deflate : .none, - acceptedEncodings: [.deflate] - ) - ), - maxPayloadSize: 100, - skipAssertions: true - ) - - let serverMetadata: HPACKHeaders = - compressionEnabled ? .serverInitialMetadataWithDeflateCompression : .serverInitialMetadata - switch targetState { - case .clientIdleServerIdle: - break - case .clientOpenServerIdle: - // Open client - XCTAssertNoThrow(try stateMachine.send(metadata: [])) - case .clientOpenServerOpen: - // Open client - XCTAssertNoThrow(try stateMachine.send(metadata: [])) - // Open server - XCTAssertNoThrow(try stateMachine.receive(headers: serverMetadata, endStream: false)) - case .clientOpenServerClosed: - // Open client - XCTAssertNoThrow(try stateMachine.send(metadata: [])) - // Open server - XCTAssertNoThrow(try stateMachine.receive(headers: serverMetadata, endStream: false)) - // Close server - XCTAssertNoThrow(try stateMachine.receive(headers: .serverTrailers, endStream: true)) - case .clientClosedServerIdle: - // Open client - XCTAssertNoThrow(try stateMachine.send(metadata: [])) - // Close client - XCTAssertNoThrow(try stateMachine.closeOutbound()) - case .clientClosedServerOpen: - // Open client - XCTAssertNoThrow(try stateMachine.send(metadata: [])) - // Open server - XCTAssertNoThrow(try stateMachine.receive(headers: serverMetadata, endStream: false)) - // Close client - XCTAssertNoThrow(try stateMachine.closeOutbound()) - case .clientClosedServerClosed: - // Open client - XCTAssertNoThrow(try stateMachine.send(metadata: [])) - // Open server - XCTAssertNoThrow(try stateMachine.receive(headers: serverMetadata, endStream: false)) - // Close client - XCTAssertNoThrow(try stateMachine.closeOutbound()) - // Close server - XCTAssertNoThrow(try stateMachine.receive(headers: .serverTrailers, endStream: true)) - } - - return stateMachine - } - - // - MARK: Send Metadata - - func testSendMetadataWhenClientIdleAndServerIdle() throws { - var stateMachine = self.makeClientStateMachine(targetState: .clientIdleServerIdle) - XCTAssertNoThrow(try stateMachine.send(metadata: [])) - } - - func testSendMetadataWhenClientAlreadyOpen() throws { - for targetState in [ - TargetStateMachineState.clientOpenServerIdle, .clientOpenServerOpen, .clientOpenServerClosed, - ] { - var stateMachine = self.makeClientStateMachine(targetState: targetState) - - // Try sending metadata again: should throw - XCTAssertThrowsError( - ofType: GRPCStreamStateMachine.InvalidState.self, - try stateMachine.send(metadata: .init()) - ) { - error in - XCTAssertEqual(error.message, "Client is already open: shouldn't be sending metadata.") - } - } - } - - func testSendMetadataWhenClientAlreadyClosed() throws { - for targetState in [ - TargetStateMachineState.clientClosedServerIdle, .clientClosedServerOpen, - .clientClosedServerClosed, - ] { - var stateMachine = self.makeClientStateMachine(targetState: targetState) - - // Try sending metadata again: should throw - XCTAssertThrowsError( - ofType: GRPCStreamStateMachine.InvalidState.self, - try stateMachine.send(metadata: .init()) - ) { - error in - XCTAssertEqual(error.message, "Client is closed: can't send metadata.") - } - } - } - - // - MARK: Send Message - - func testSendMessageWhenClientIdleAndServerIdle() { - var stateMachine = self.makeClientStateMachine(targetState: .clientIdleServerIdle) - - // Try to send a message without opening (i.e. without sending initial metadata) - XCTAssertThrowsError( - ofType: GRPCStreamStateMachine.InvalidState.self, - try stateMachine.send(message: [], promise: nil) - ) { error in - XCTAssertEqual(error.message, "Client not yet open.") - } - } - - func testSendMessageWhenClientOpen() { - for targetState in [ - TargetStateMachineState.clientOpenServerIdle, .clientOpenServerOpen, .clientOpenServerClosed, - ] { - var stateMachine = self.makeClientStateMachine(targetState: targetState) - - // Now send a message - XCTAssertNoThrow(try stateMachine.send(message: [], promise: nil)) - } - } - - func testSendMessageWhenClientClosed() { - for targetState in [ - TargetStateMachineState.clientClosedServerIdle, .clientClosedServerOpen, - .clientClosedServerClosed, - ] { - var stateMachine = self.makeClientStateMachine(targetState: targetState) - - // Try sending another message: it should fail - XCTAssertThrowsError( - ofType: GRPCStreamStateMachine.InvalidState.self, - try stateMachine.send(message: [], promise: nil) - ) { error in - XCTAssertEqual(error.message, "Client is closed, cannot send a message.") - } - } - } - - // - MARK: Send Status and Trailers - - func testSendStatusAndTrailers() { - for targetState in TargetStateMachineState.allCases { - var stateMachine = self.makeClientStateMachine(targetState: targetState) - - // This operation is never allowed on the client. - XCTAssertThrowsError( - ofType: GRPCStreamStateMachine.InvalidState.self, - try stateMachine.send( - status: Status(code: .ok, message: ""), - metadata: .init() - ) - ) { error in - XCTAssertEqual(error.message, "Client cannot send status and trailer.") - } - } - } - - // - MARK: Receive initial metadata - - func testReceiveInitialMetadataWhenClientIdleAndServerIdle() { - var stateMachine = self.makeClientStateMachine(targetState: .clientIdleServerIdle) - - XCTAssertThrowsError( - ofType: GRPCStreamStateMachine.InvalidState.self, - try stateMachine.receive(headers: .init(), endStream: false) - ) { error in - XCTAssertEqual(error.message, "Server cannot have sent metadata if the client is idle.") - } - } - - func testReceiveInvalidInitialMetadataWhenServerIdle() throws { - for targetState in [ - TargetStateMachineState.clientOpenServerIdle, .clientClosedServerIdle, - ] { - var stateMachine = self.makeClientStateMachine(targetState: targetState) - - // Receive metadata with unexpected non-200 status code - let action = try stateMachine.receive( - headers: [GRPCHTTP2Keys.status.rawValue: "300"], - endStream: false - ) - - XCTAssertEqual( - action, - .receivedStatusAndMetadata_clientOnly( - status: .init(code: .unknown, message: "Unexpected non-200 HTTP Status Code."), - metadata: [":status": "300"] - ) - ) - } - } - - func testReceiveInitialMetadataWhenServerIdle_ClientUnsupportedEncoding() throws { - // Create client with deflate compression enabled - var stateMachine = self.makeClientStateMachine( - targetState: .clientOpenServerIdle, - compressionEnabled: true - ) - - // Try opening server with gzip compression, which client does not support. - let action = try stateMachine.receive( - headers: .serverInitialMetadataWithGZIPCompression, - endStream: false - ) - - XCTAssertEqual( - action, - .receivedStatusAndMetadata_clientOnly( - status: Status( - code: .internalError, - message: - "The server picked a compression algorithm ('gzip') the client does not know about." - ), - metadata: [ - ":status": "200", - "content-type": "application/grpc", - "grpc-encoding": "gzip", - ] - ) - ) - } - - func testReceiveMessage_ClientCompressionEnabled() throws { - // Enable deflate compression on client - var stateMachine = self.makeClientStateMachine( - targetState: .clientOpenServerOpen, - compressionEnabled: true - ) - - let originalMessage = [UInt8]([42, 42, 43, 43]) - - // Receiving uncompressed message should still work. - let receivedUncompressedBytes = try self.frameMessage(originalMessage, compression: .none) - XCTAssertNoThrow(try stateMachine.receive(buffer: receivedUncompressedBytes, endStream: false)) - var receivedAction = stateMachine.nextInboundMessage() - switch receivedAction { - case .noMoreMessages, .awaitMoreMessages: - XCTFail("Should have received message") - case .receiveMessage(let receivedMessaged): - XCTAssertEqual(originalMessage, receivedMessaged) - } - - // Receiving compressed message with deflate should work - let receivedDeflateCompressedBytes = try self.frameMessage( - originalMessage, - compression: .deflate - ) - XCTAssertNoThrow( - try stateMachine.receive(buffer: receivedDeflateCompressedBytes, endStream: false) - ) - receivedAction = stateMachine.nextInboundMessage() - switch receivedAction { - case .noMoreMessages, .awaitMoreMessages: - XCTFail("Should have received message") - case .receiveMessage(let receivedMessaged): - XCTAssertEqual(originalMessage, receivedMessaged) - } - - // Receiving compressed message with gzip (unsupported) should throw error - let receivedGZIPCompressedBytes = try self.frameMessage(originalMessage, compression: .gzip) - let action = try stateMachine.receive(buffer: receivedGZIPCompressedBytes, endStream: false) - XCTAssertEqual( - action, - .endRPCAndForwardErrorStatus_clientOnly( - Status(code: .internalError, message: "Failed to decode message") - ) - ) - - receivedAction = stateMachine.nextInboundMessage() - switch receivedAction { - case .awaitMoreMessages: - () - case .noMoreMessages: - XCTFail("Should be awaiting for more messages") - case .receiveMessage: - XCTFail("Should not have received message") - } - } - - func testReceiveInitialMetadataWhenServerIdle() throws { - for targetState in [ - TargetStateMachineState.clientOpenServerIdle, .clientClosedServerIdle, - ] { - var stateMachine = self.makeClientStateMachine(targetState: targetState) - - // Receive metadata = open server - let action = try stateMachine.receive( - headers: [ - GRPCHTTP2Keys.status.rawValue: "200", - GRPCHTTP2Keys.contentType.rawValue: ContentType.grpc.canonicalValue, - GRPCHTTP2Keys.encoding.rawValue: "deflate", - "custom": "123", - "custom-bin": String(base64Encoding: [42, 43, 44]), - ], - endStream: false - ) - - var expectedMetadata: Metadata = [ - ":status": "200", - "content-type": "application/grpc", - "grpc-encoding": "deflate", - "custom": "123", - ] - expectedMetadata.addBinary([42, 43, 44], forKey: "custom-bin") - XCTAssertEqual(action, .receivedMetadata(expectedMetadata, nil)) - } - } - - func testReceiveInitialMetadataWhenServerOpen() throws { - for targetState in [ - TargetStateMachineState.clientOpenServerOpen, .clientClosedServerOpen, - ] { - var stateMachine = self.makeClientStateMachine(targetState: targetState) - - let action1 = try stateMachine.receive( - headers: [ - GRPCHTTP2Keys.status.rawValue: "200", - GRPCHTTP2Keys.contentType.rawValue: ContentType.grpc.canonicalValue, - GRPCHTTP2Keys.encoding.rawValue: "deflate", - "custom": "123", - "custom-bin": String(base64Encoding: [42, 43, 44]), - ], - endStream: false - ) - - let expectedStatus = Status(code: .unknown, message: "No 'grpc-status' value in trailers") - let expectedMetadata: Metadata = [ - ":status": "200", - "content-type": "application/grpc", - "grpc-encoding": "deflate", - "custom": "123", - "custom-bin": .binary([42, 43, 44]), - ] - - XCTAssertEqual( - action1, - .receivedStatusAndMetadata_clientOnly(status: expectedStatus, metadata: expectedMetadata) - ) - - // Now make sure everything works well if we include grpc-status - let action2 = try stateMachine.receive( - headers: [ - GRPCHTTP2Keys.status.rawValue: "200", - GRPCHTTP2Keys.grpcStatus.rawValue: String(Status.Code.ok.rawValue), - GRPCHTTP2Keys.contentType.rawValue: ContentType.grpc.canonicalValue, - GRPCHTTP2Keys.encoding.rawValue: "deflate", - "custom": "123", - "custom-bin": String(base64Encoding: [42, 43, 44]), - ], - endStream: false - ) - - XCTAssertEqual( - action2, - .receivedStatusAndMetadata_clientOnly( - status: Status(code: .ok, message: ""), - metadata: expectedMetadata - ) - ) - } - } - - func testReceiveInitialMetadataWhenServerClosed() { - for targetState in [TargetStateMachineState.clientOpenServerClosed, .clientClosedServerClosed] { - var stateMachine = self.makeClientStateMachine(targetState: targetState) - - XCTAssertThrowsError( - ofType: GRPCStreamStateMachine.InvalidState.self, - try stateMachine.receive(headers: .init(), endStream: false) - ) { error in - XCTAssertEqual(error.message, "Server is closed, nothing could have been sent.") - } - } - } - - // - MARK: Receive end trailers - - func testReceiveEndTrailerWhenClientIdleAndServerIdle() { - var stateMachine = self.makeClientStateMachine(targetState: .clientIdleServerIdle) - - // Receive an end trailer - XCTAssertThrowsError( - ofType: GRPCStreamStateMachine.InvalidState.self, - try stateMachine.receive(headers: .init(), endStream: true) - ) { error in - XCTAssertEqual(error.message, "Server cannot have sent metadata if the client is idle.") - } - } - - func testReceiveEndTrailerWhenClientOpenAndServerIdle() throws { - var stateMachine = self.makeClientStateMachine(targetState: .clientOpenServerIdle) - - // Receive a trailers-only response - let trailersOnlyResponse: HPACKHeaders = [ - GRPCHTTP2Keys.status.rawValue: "200", - GRPCHTTP2Keys.contentType.rawValue: ContentType.grpc.canonicalValue, - GRPCHTTP2Keys.grpcStatus.rawValue: String(Status.Code.internalError.rawValue), - GRPCHTTP2Keys.grpcStatusMessage.rawValue: GRPCStatusMessageMarshaller.marshall( - "Some, status, message" - )!, - "custom-key": "custom-value", - ] - let trailers = try stateMachine.receive(headers: trailersOnlyResponse, endStream: true) - switch trailers { - case .receivedStatusAndMetadata_clientOnly(let status, let metadata): - XCTAssertEqual(status, Status(code: .internalError, message: "Some, status, message")) - XCTAssertEqual( - metadata, - [ - ":status": "200", - "content-type": "application/grpc", - "custom-key": "custom-value", - ] - ) - case .receivedMetadata, .doNothing, .rejectRPC_serverOnly, .protocolViolation_serverOnly: - XCTFail("Expected .receivedStatusAndMetadata") - } - } - - func testReceiveEndTrailerWhenServerOpen() throws { - for targetState in [TargetStateMachineState.clientOpenServerOpen, .clientClosedServerOpen] { - var stateMachine = self.makeClientStateMachine(targetState: targetState) - - // Receive an end trailer - let action = try stateMachine.receive( - headers: [ - GRPCHTTP2Keys.status.rawValue: "200", - GRPCHTTP2Keys.grpcStatus.rawValue: String(Status.Code.ok.rawValue), - GRPCHTTP2Keys.contentType.rawValue: ContentType.grpc.canonicalValue, - GRPCHTTP2Keys.encoding.rawValue: "deflate", - "custom": "123", - ], - endStream: true - ) - - let expectedMetadata: Metadata = [ - ":status": "200", - "content-type": "application/grpc", - "grpc-encoding": "deflate", - "custom": "123", - ] - XCTAssertEqual( - action, - .receivedStatusAndMetadata_clientOnly( - status: .init(code: .ok, message: ""), - metadata: expectedMetadata - ) - ) - } - } - - func testReceiveEndTrailerWhenClientOpenAndServerClosed() { - var stateMachine = self.makeClientStateMachine(targetState: .clientOpenServerClosed) - - // Receive another end trailer - XCTAssertThrowsError( - ofType: GRPCStreamStateMachine.InvalidState.self, - try stateMachine.receive(headers: .init(), endStream: true) - ) { error in - XCTAssertEqual(error.message, "Server is closed, nothing could have been sent.") - } - } - - func testReceiveEndTrailerWhenClientClosedAndServerIdle() throws { - var stateMachine = self.makeClientStateMachine(targetState: .clientClosedServerIdle) - - // Server sends a trailers-only response - let trailersOnlyResponse: HPACKHeaders = [ - GRPCHTTP2Keys.status.rawValue: "200", - GRPCHTTP2Keys.contentType.rawValue: ContentType.grpc.canonicalValue, - GRPCHTTP2Keys.grpcStatus.rawValue: String(Status.Code.internalError.rawValue), - GRPCHTTP2Keys.grpcStatusMessage.rawValue: GRPCStatusMessageMarshaller.marshall( - "Some status message" - )!, - "custom-key": "custom-value", - ] - let trailers = try stateMachine.receive(headers: trailersOnlyResponse, endStream: true) - switch trailers { - case .receivedStatusAndMetadata_clientOnly(let status, let metadata): - XCTAssertEqual(status, Status(code: .internalError, message: "Some status message")) - XCTAssertEqual( - metadata, - [ - ":status": "200", - "content-type": "application/grpc", - "custom-key": "custom-value", - ] - ) - case .receivedMetadata, .doNothing, .rejectRPC_serverOnly, .protocolViolation_serverOnly: - XCTFail("Expected .receivedStatusAndMetadata") - } - } - - func testReceiveEndTrailerWhenClientClosedAndServerClosed() { - var stateMachine = self.makeClientStateMachine(targetState: .clientClosedServerClosed) - - // Close server again (endStream = true) and assert we don't throw. - // This can happen if the previous close was caused by a grpc-status header - // and then the server sends an empty frame with EOS set. - XCTAssertEqual(try stateMachine.receive(headers: .init(), endStream: true), .doNothing) - } - - // - MARK: Receive message - - func testReceiveMessageWhenClientIdleAndServerIdle() { - var stateMachine = self.makeClientStateMachine(targetState: .clientIdleServerIdle) - - XCTAssertThrowsError( - ofType: GRPCStreamStateMachine.InvalidState.self, - try stateMachine.receive(buffer: .init(), endStream: false) - ) { error in - XCTAssertEqual( - error.message, - "Cannot have received anything from server if client is not yet open." - ) - } - } - - func testReceiveMessageWhenServerIdle() { - for targetState in [TargetStateMachineState.clientOpenServerIdle, .clientClosedServerIdle] { - var stateMachine = self.makeClientStateMachine(targetState: targetState) - - XCTAssertThrowsError( - ofType: GRPCStreamStateMachine.InvalidState.self, - try stateMachine.receive(buffer: .init(), endStream: false) - ) { error in - XCTAssertEqual( - error.message, - "Server cannot have sent a message before sending the initial metadata." - ) - } - } - } - - func testReceiveMessageWhenServerOpen() throws { - for targetState in [TargetStateMachineState.clientOpenServerOpen, .clientClosedServerOpen] { - var stateMachine = self.makeClientStateMachine(targetState: targetState) - - XCTAssertEqual( - try stateMachine.receive(buffer: .init(), endStream: false), - .readInbound - ) - XCTAssertEqual( - try stateMachine.receive(buffer: .init(), endStream: true), - .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. - """ - ) - ) - ) - } - } - - func testReceiveMessageWhenServerClosed() { - for targetState in [TargetStateMachineState.clientOpenServerClosed, .clientClosedServerClosed] { - var stateMachine = self.makeClientStateMachine(targetState: targetState) - - XCTAssertThrowsError( - ofType: GRPCStreamStateMachine.InvalidState.self, - try stateMachine.receive(buffer: .init(), endStream: false) - ) { error in - XCTAssertEqual(error.message, "Cannot have received anything from a closed server.") - } - } - } - - // - MARK: Next outbound message - - func testNextOutboundMessageWhenClientIdleAndServerIdle() { - var stateMachine = self.makeClientStateMachine(targetState: .clientIdleServerIdle) - - XCTAssertThrowsError( - ofType: GRPCStreamStateMachine.InvalidState.self, - try stateMachine.nextOutboundFrame() - ) { error in - XCTAssertEqual(error.message, "Client is not open yet.") - } - } - - func testNextOutboundMessageWhenClientOpenAndServerOpenOrIdle() throws { - for targetState in [TargetStateMachineState.clientOpenServerIdle, .clientOpenServerOpen] { - var stateMachine = self.makeClientStateMachine(targetState: targetState) - - XCTAssertEqual(try stateMachine.nextOutboundFrame(), .awaitMoreMessages) - - XCTAssertNoThrow(try stateMachine.send(message: [42, 42], promise: nil)) - - let expectedBytes: [UInt8] = [ - 0, // compression flag: unset - 0, 0, 0, 2, // message length: 2 bytes - 42, 42, // original message - ] - XCTAssertEqual( - try stateMachine.nextOutboundFrame(), - .sendFrame(frame: ByteBuffer(bytes: expectedBytes), promise: nil) - ) - - // And then make sure that nothing else is returned anymore - XCTAssertEqual(try stateMachine.nextOutboundFrame(), .awaitMoreMessages) - } - } - - func testNextOutboundMessageWhenClientOpenAndServerIdle_WithCompression() throws { - var stateMachine = self.makeClientStateMachine( - targetState: .clientOpenServerIdle, - compressionEnabled: true - ) - - XCTAssertEqual(try stateMachine.nextOutboundFrame(), .awaitMoreMessages) - - let originalMessage = [UInt8]([42, 42, 43, 43]) - XCTAssertNoThrow(try stateMachine.send(message: originalMessage, promise: nil)) - - let request = try stateMachine.nextOutboundFrame() - let framedMessage = try self.frameMessage(originalMessage, compression: .deflate) - XCTAssertEqual(request, .sendFrame(frame: framedMessage, promise: nil)) - } - - func testNextOutboundMessageWhenClientOpenAndServerOpen_WithCompression() throws { - var stateMachine = self.makeClientStateMachine( - targetState: .clientOpenServerOpen, - compressionEnabled: true - ) - - XCTAssertEqual(try stateMachine.nextOutboundFrame(), .awaitMoreMessages) - - let originalMessage = [UInt8]([42, 42, 43, 43]) - XCTAssertNoThrow(try stateMachine.send(message: originalMessage, promise: nil)) - - let request = try stateMachine.nextOutboundFrame() - let framedMessage = try self.frameMessage(originalMessage, compression: .deflate) - XCTAssertEqual(request, .sendFrame(frame: framedMessage, promise: nil)) - } - - func testNextOutboundMessageWhenClientOpenAndServerClosed() throws { - var stateMachine = self.makeClientStateMachine(targetState: .clientOpenServerClosed) - - // No more messages to send - XCTAssertEqual(try stateMachine.nextOutboundFrame(), .noMoreMessages) - - // Queue a message, but assert the action is .noMoreMessages nevertheless, - // because the server is closed. - XCTAssertNoThrow(try stateMachine.send(message: [42, 42], promise: nil)) - XCTAssertEqual(try stateMachine.nextOutboundFrame(), .noMoreMessages) - } - - func testNextOutboundMessageWhenClientClosedAndServerIdle() throws { - var stateMachine = self.makeClientStateMachine(targetState: .clientOpenServerIdle) - - // Send a message and close client - XCTAssertNoThrow(try stateMachine.send(message: [42, 42], promise: nil)) - XCTAssertNoThrow(try stateMachine.closeOutbound()) - - // Make sure that getting the next outbound message _does_ return the message - // we have enqueued. - let request = try stateMachine.nextOutboundFrame() - let expectedBytes: [UInt8] = [ - 0, // compression flag: unset - 0, 0, 0, 2, // message length: 2 bytes - 42, 42, // original message - ] - XCTAssertEqual(request, .sendFrame(frame: ByteBuffer(bytes: expectedBytes), promise: nil)) - - // And then make sure that nothing else is returned anymore - XCTAssertEqual(try stateMachine.nextOutboundFrame(), .noMoreMessages) - } - - func testNextOutboundMessageWhenClientClosedAndServerOpen() throws { - var stateMachine = self.makeClientStateMachine(targetState: .clientOpenServerOpen) - - // Send a message and close client - XCTAssertNoThrow(try stateMachine.send(message: [42, 42], promise: nil)) - XCTAssertNoThrow(try stateMachine.closeOutbound()) - - // Make sure that getting the next outbound message _does_ return the message - // we have enqueued. - let request = try stateMachine.nextOutboundFrame() - let expectedBytes: [UInt8] = [ - 0, // compression flag: unset - 0, 0, 0, 2, // message length: 2 bytes - 42, 42, // original message - ] - XCTAssertEqual(request, .sendFrame(frame: ByteBuffer(bytes: expectedBytes), promise: nil)) - - // And then make sure that nothing else is returned anymore - XCTAssertEqual(try stateMachine.nextOutboundFrame(), .noMoreMessages) - } - - func testNextOutboundMessageWhenClientClosedAndServerClosed() throws { - var stateMachine = self.makeClientStateMachine(targetState: .clientOpenServerOpen) - // Send a message - XCTAssertNoThrow(try stateMachine.send(message: [42, 42], promise: nil)) - - // Close server - XCTAssertNoThrow(try stateMachine.receive(headers: .serverTrailers, endStream: true)) - - // Close client - XCTAssertNoThrow(try stateMachine.closeOutbound()) - - // Even though we have enqueued a message, don't send it, because the server - // is closed. - XCTAssertEqual(try stateMachine.nextOutboundFrame(), .noMoreMessages) - } - - // - MARK: Next inbound message - - func testNextInboundMessageWhenServerIdle() { - for targetState in [ - TargetStateMachineState.clientIdleServerIdle, .clientOpenServerIdle, .clientClosedServerIdle, - ] { - var stateMachine = self.makeClientStateMachine(targetState: targetState) - XCTAssertEqual(stateMachine.nextInboundMessage(), .awaitMoreMessages) - } - } - - func testNextInboundMessageWhenClientOpenAndServerOpen() throws { - var stateMachine = self.makeClientStateMachine(targetState: .clientOpenServerOpen) - - let receivedBytes = ByteBuffer(bytes: [ - 0, // compression flag: unset - 0, 0, 0, 2, // message length: 2 bytes - 42, 42, // original message - ]) - XCTAssertEqual( - try stateMachine.receive(buffer: receivedBytes, endStream: false), - .readInbound - ) - - XCTAssertEqual(stateMachine.nextInboundMessage(), .receiveMessage([42, 42])) - XCTAssertEqual(stateMachine.nextInboundMessage(), .awaitMoreMessages) - } - - func testNextInboundMessageWhenClientOpenAndServerOpen_WithCompression() throws { - var stateMachine = self.makeClientStateMachine( - targetState: .clientOpenServerOpen, - compressionEnabled: true - ) - - let originalMessage = [UInt8]([42, 42, 43, 43]) - let receivedBytes = try self.frameMessage(originalMessage, compression: .deflate) - XCTAssertEqual( - try stateMachine.receive(buffer: receivedBytes, endStream: false), - .readInbound - ) - - XCTAssertEqual(stateMachine.nextInboundMessage(), .receiveMessage(originalMessage)) - XCTAssertEqual(stateMachine.nextInboundMessage(), .awaitMoreMessages) - } - - func testNextInboundMessageWhenClientOpenAndServerClosed() throws { - var stateMachine = self.makeClientStateMachine(targetState: .clientOpenServerOpen) - - let receivedBytes = ByteBuffer(bytes: [ - 0, // compression flag: unset - 0, 0, 0, 2, // message length: 2 bytes - 42, 42, // original message - ]) - XCTAssertEqual( - try stateMachine.receive(buffer: receivedBytes, endStream: false), - .readInbound - ) - - // Close server - XCTAssertNoThrow(try stateMachine.receive(headers: .serverTrailers, endStream: true)) - - XCTAssertEqual(stateMachine.nextInboundMessage(), .receiveMessage([42, 42])) - XCTAssertEqual(stateMachine.nextInboundMessage(), .noMoreMessages) - } - - func testNextInboundMessageWhenClientClosedAndServerOpen() throws { - var stateMachine = self.makeClientStateMachine(targetState: .clientOpenServerOpen) - - let receivedBytes = ByteBuffer(bytes: [ - 0, // compression flag: unset - 0, 0, 0, 2, // message length: 2 bytes - 42, 42, // original message - ]) - XCTAssertEqual( - try stateMachine.receive(buffer: receivedBytes, endStream: false), - .readInbound - ) - - // Close client - XCTAssertNoThrow(try stateMachine.closeOutbound()) - - // Even though the client is closed, because it received a message while open, - // we must get the message now. - XCTAssertEqual(stateMachine.nextInboundMessage(), .receiveMessage([42, 42])) - XCTAssertEqual(stateMachine.nextInboundMessage(), .awaitMoreMessages) - } - - func testNextInboundMessageWhenClientClosedAndServerClosed() throws { - var stateMachine = self.makeClientStateMachine(targetState: .clientOpenServerOpen) - - let receivedBytes = ByteBuffer(bytes: [ - 0, // compression flag: unset - 0, 0, 0, 2, // message length: 2 bytes - 42, 42, // original message - ]) - XCTAssertEqual( - try stateMachine.receive(buffer: receivedBytes, endStream: false), - .readInbound - ) - - // Close server - XCTAssertNoThrow(try stateMachine.receive(headers: .serverTrailers, endStream: true)) - - // Close client - XCTAssertNoThrow(try stateMachine.closeOutbound()) - - // Even though the client is closed, because it received a message while open, - // we must get the message now. - XCTAssertEqual(stateMachine.nextInboundMessage(), .receiveMessage([42, 42])) - XCTAssertEqual(stateMachine.nextInboundMessage(), .noMoreMessages) - } - - // - MARK: Unexpected close - - func testUnexpectedCloseWhenServerIdleOrOpen() throws { - let thrownError = RPCError(code: .deadlineExceeded, message: "Test error") - let reasonAndExpectedStatusPairs = [ - ( - GRPCStreamStateMachine.UnexpectedInboundCloseReason.channelInactive, - Status(code: .unavailable, message: "Stream unexpectedly closed.") - ), - ( - GRPCStreamStateMachine.UnexpectedInboundCloseReason.streamReset, - Status( - code: .unavailable, - message: "Stream unexpectedly closed: a RST_STREAM frame was received." - ) - ), - ( - GRPCStreamStateMachine.UnexpectedInboundCloseReason.errorThrown(thrownError), - Status( - code: .unavailable, - message: "Stream unexpectedly closed with error." - ) - ), - ] - let states = [ - TargetStateMachineState.clientIdleServerIdle, - .clientOpenServerIdle, - .clientOpenServerOpen, - .clientClosedServerIdle, - .clientClosedServerOpen, - ] - - for state in states { - for (closeReason, expectedStatus) in reasonAndExpectedStatusPairs { - var stateMachine = self.makeClientStateMachine(targetState: state) - var action = stateMachine.unexpectedInboundClose(reason: closeReason) - - guard case .forwardStatus_clientOnly(let status) = action else { - XCTFail("Should have been `fireError` but was `\(action)` (state: \(state)).") - return - } - XCTAssertEqual(status, expectedStatus) - - // Calling unexpectedInboundClose again should return `doNothing` because - // we're already closed. - action = stateMachine.unexpectedInboundClose(reason: closeReason) - guard case .doNothing = action else { - XCTFail("Should have been `doNothing` but was `\(action)` (state: \(state)).") - return - } - } - } - } - - func testUnexpectedCloseWhenServerClosed() throws { - let closeReasons = [ - GRPCStreamStateMachine.UnexpectedInboundCloseReason.channelInactive, - .streamReset, - .errorThrown(RPCError(code: .deadlineExceeded, message: "Test error")), - ] - let states = [ - TargetStateMachineState.clientOpenServerClosed, - .clientClosedServerClosed, - ] - - for state in states { - for closeReason in closeReasons { - var stateMachine = self.makeClientStateMachine(targetState: state) - var action = stateMachine.unexpectedInboundClose(reason: closeReason) - guard case .doNothing = action else { - XCTFail("Should have been `doNothing` but was `\(action)` (state: \(state)).") - return - } - - // Calling unexpectedInboundClose again should return `doNothing` again. - action = stateMachine.unexpectedInboundClose(reason: closeReason) - guard case .doNothing = action else { - XCTFail("Should have been `doNothing` but was `\(action)` (state: \(state)).") - return - } - } - } - } - - // - MARK: Common paths - - func testNormalFlow() throws { - var stateMachine = self.makeClientStateMachine(targetState: .clientIdleServerIdle) - - // Client sends metadata - let clientInitialMetadata = try stateMachine.send(metadata: .init()) - XCTAssertEqual( - clientInitialMetadata, - [ - GRPCHTTP2Keys.path.rawValue: "/test/test", - GRPCHTTP2Keys.scheme.rawValue: "http", - GRPCHTTP2Keys.method.rawValue: "POST", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - GRPCHTTP2Keys.te.rawValue: "trailers", - GRPCHTTP2Keys.acceptEncoding.rawValue: "deflate", - ] - ) - - // Server sends initial metadata - let serverInitialHeadersAction = try stateMachine.receive( - headers: .serverInitialMetadata, - endStream: false - ) - XCTAssertEqual( - serverInitialHeadersAction, - .receivedMetadata( - [ - ":status": "200", - "content-type": "application/grpc", - ], - nil - ) - ) - - // Client sends messages - XCTAssertEqual(try stateMachine.nextOutboundFrame(), .awaitMoreMessages) - - let message = [UInt8]([1, 2, 3, 4]) - let framedMessage = try self.frameMessage(message, compression: .none) - try stateMachine.send(message: message, promise: nil) - XCTAssertEqual( - try stateMachine.nextOutboundFrame(), - .sendFrame(frame: framedMessage, promise: nil) - ) - XCTAssertEqual(try stateMachine.nextOutboundFrame(), .awaitMoreMessages) - - // Server sends response - XCTAssertEqual(stateMachine.nextInboundMessage(), .awaitMoreMessages) - - let firstResponseBytes = [UInt8]([5, 6, 7]) - let firstResponse = try self.frameMessage(firstResponseBytes, compression: .none) - let secondResponseBytes = [UInt8]([8, 9, 10]) - let secondResponse = try self.frameMessage(secondResponseBytes, compression: .none) - XCTAssertEqual( - try stateMachine.receive(buffer: firstResponse, endStream: false), - .readInbound - ) - XCTAssertEqual( - try stateMachine.receive(buffer: secondResponse, endStream: false), - .readInbound - ) - - // Make sure messages have arrived - XCTAssertEqual(stateMachine.nextInboundMessage(), .receiveMessage(firstResponseBytes)) - XCTAssertEqual(stateMachine.nextInboundMessage(), .receiveMessage(secondResponseBytes)) - XCTAssertEqual(stateMachine.nextInboundMessage(), .awaitMoreMessages) - - // Client sends end - XCTAssertNoThrow(try stateMachine.closeOutbound()) - - // Server ends - let metadataReceivedAction = try stateMachine.receive( - headers: .serverTrailers, - endStream: true - ) - let receivedMetadata = { - var m = Metadata(headers: .serverTrailers) - m.removeAllValues(forKey: GRPCHTTP2Keys.grpcStatus.rawValue) - m.removeAllValues(forKey: GRPCHTTP2Keys.grpcStatusMessage.rawValue) - return m - }() - XCTAssertEqual( - metadataReceivedAction, - .receivedStatusAndMetadata_clientOnly( - status: .init(code: .ok, message: ""), - metadata: receivedMetadata - ) - ) - - XCTAssertEqual(try stateMachine.nextOutboundFrame(), .noMoreMessages) - XCTAssertEqual(stateMachine.nextInboundMessage(), .noMoreMessages) - } - - func testClientClosesBeforeItCanOpen() throws { - var stateMachine = self.makeClientStateMachine(targetState: .clientIdleServerIdle) - XCTAssertNoThrow(try stateMachine.closeOutbound()) - } - - func testClientClosesBeforeServerOpens() throws { - var stateMachine = self.makeClientStateMachine(targetState: .clientIdleServerIdle) - - // Client sends metadata - let clientInitialMetadata = try stateMachine.send(metadata: .init()) - XCTAssertEqual( - clientInitialMetadata, - [ - GRPCHTTP2Keys.path.rawValue: "/test/test", - GRPCHTTP2Keys.scheme.rawValue: "http", - GRPCHTTP2Keys.method.rawValue: "POST", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - GRPCHTTP2Keys.te.rawValue: "trailers", - GRPCHTTP2Keys.acceptEncoding.rawValue: "deflate", - ] - ) - - // Client sends messages and ends - XCTAssertEqual(try stateMachine.nextOutboundFrame(), .awaitMoreMessages) - - let message = [UInt8]([1, 2, 3, 4]) - let framedMessage = try self.frameMessage(message, compression: .none) - XCTAssertNoThrow(try stateMachine.send(message: message, promise: nil)) - XCTAssertNoThrow(try stateMachine.closeOutbound()) - XCTAssertEqual( - try stateMachine.nextOutboundFrame(), - .sendFrame(frame: framedMessage, promise: nil) - ) - XCTAssertEqual(try stateMachine.nextOutboundFrame(), .noMoreMessages) - - // Server sends initial metadata - let serverInitialHeadersAction = try stateMachine.receive( - headers: .serverInitialMetadata, - endStream: false - ) - XCTAssertEqual( - serverInitialHeadersAction, - .receivedMetadata( - [ - ":status": "200", - "content-type": "application/grpc", - ], - nil - ) - ) - - // Server sends response - XCTAssertEqual(stateMachine.nextInboundMessage(), .awaitMoreMessages) - - let firstResponseBytes = [UInt8]([5, 6, 7]) - let firstResponse = try self.frameMessage(firstResponseBytes, compression: .none) - let secondResponseBytes = [UInt8]([8, 9, 10]) - let secondResponse = try self.frameMessage(secondResponseBytes, compression: .none) - XCTAssertEqual( - try stateMachine.receive(buffer: firstResponse, endStream: false), - .readInbound - ) - XCTAssertEqual( - try stateMachine.receive(buffer: secondResponse, endStream: false), - .readInbound - ) - - // Make sure messages have arrived - XCTAssertEqual(stateMachine.nextInboundMessage(), .receiveMessage(firstResponseBytes)) - XCTAssertEqual(stateMachine.nextInboundMessage(), .receiveMessage(secondResponseBytes)) - XCTAssertEqual(stateMachine.nextInboundMessage(), .awaitMoreMessages) - - // Server ends - let metadataReceivedAction = try stateMachine.receive( - headers: .serverTrailers, - endStream: true - ) - let receivedMetadata = { - var m = Metadata(headers: .serverTrailers) - m.removeAllValues(forKey: GRPCHTTP2Keys.grpcStatus.rawValue) - m.removeAllValues(forKey: GRPCHTTP2Keys.grpcStatusMessage.rawValue) - return m - }() - XCTAssertEqual( - metadataReceivedAction, - .receivedStatusAndMetadata_clientOnly( - status: .init(code: .ok, message: ""), - metadata: receivedMetadata - ) - ) - - XCTAssertEqual(try stateMachine.nextOutboundFrame(), .noMoreMessages) - XCTAssertEqual(stateMachine.nextInboundMessage(), .noMoreMessages) - } - - func testClientClosesBeforeServerResponds() throws { - var stateMachine = self.makeClientStateMachine(targetState: .clientIdleServerIdle) - - // Client sends metadata - let clientInitialMetadata = try stateMachine.send(metadata: .init()) - XCTAssertEqual( - clientInitialMetadata, - [ - GRPCHTTP2Keys.path.rawValue: "/test/test", - GRPCHTTP2Keys.scheme.rawValue: "http", - GRPCHTTP2Keys.method.rawValue: "POST", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - GRPCHTTP2Keys.te.rawValue: "trailers", - GRPCHTTP2Keys.acceptEncoding.rawValue: "deflate", - ] - ) - - // Client sends messages - XCTAssertEqual(try stateMachine.nextOutboundFrame(), .awaitMoreMessages) - - let message = [UInt8]([1, 2, 3, 4]) - let framedMessage = try self.frameMessage(message, compression: .none) - try stateMachine.send(message: message, promise: nil) - XCTAssertEqual( - try stateMachine.nextOutboundFrame(), - .sendFrame(frame: framedMessage, promise: nil) - ) - XCTAssertEqual(try stateMachine.nextOutboundFrame(), .awaitMoreMessages) - - // Server sends initial metadata - let serverInitialHeadersAction = try stateMachine.receive( - headers: .serverInitialMetadata, - endStream: false - ) - XCTAssertEqual( - serverInitialHeadersAction, - .receivedMetadata( - [ - ":status": "200", - "content-type": "application/grpc", - ], - nil - ) - ) - - // Client closes - XCTAssertNoThrow(try stateMachine.closeOutbound()) - - // Server sends response - XCTAssertEqual(stateMachine.nextInboundMessage(), .awaitMoreMessages) - - let firstResponseBytes = [UInt8]([5, 6, 7]) - let firstResponse = try self.frameMessage(firstResponseBytes, compression: .none) - let secondResponseBytes = [UInt8]([8, 9, 10]) - let secondResponse = try self.frameMessage(secondResponseBytes, compression: .none) - XCTAssertEqual( - try stateMachine.receive(buffer: firstResponse, endStream: false), - .readInbound - ) - XCTAssertEqual( - try stateMachine.receive(buffer: secondResponse, endStream: false), - .readInbound - ) - - // Make sure messages have arrived - XCTAssertEqual(stateMachine.nextInboundMessage(), .receiveMessage(firstResponseBytes)) - XCTAssertEqual(stateMachine.nextInboundMessage(), .receiveMessage(secondResponseBytes)) - XCTAssertEqual(stateMachine.nextInboundMessage(), .awaitMoreMessages) - - // Server ends - let metadataReceivedAction = try stateMachine.receive( - headers: .serverTrailers, - endStream: true - ) - let receivedMetadata = { - var m = Metadata(headers: .serverTrailers) - m.removeAllValues(forKey: GRPCHTTP2Keys.grpcStatus.rawValue) - m.removeAllValues(forKey: GRPCHTTP2Keys.grpcStatusMessage.rawValue) - return m - }() - XCTAssertEqual( - metadataReceivedAction, - .receivedStatusAndMetadata_clientOnly( - status: .init(code: .ok, message: ""), - metadata: receivedMetadata - ) - ) - - XCTAssertEqual(try stateMachine.nextOutboundFrame(), .noMoreMessages) - XCTAssertEqual(stateMachine.nextInboundMessage(), .noMoreMessages) - } -} - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -final class GRPCStreamServerStateMachineTests: XCTestCase { - private func makeServerStateMachine( - targetState: TargetStateMachineState, - deflateCompressionEnabled: Bool = false - ) -> GRPCStreamStateMachine { - - var stateMachine = GRPCStreamStateMachine( - configuration: .server( - .init( - scheme: .http, - acceptedEncodings: deflateCompressionEnabled ? [.deflate] : [] - ) - ), - maxPayloadSize: 100, - skipAssertions: true - ) - - let clientMetadata: HPACKHeaders = - deflateCompressionEnabled - ? .clientInitialMetadataWithDeflateCompression : .clientInitialMetadata - switch targetState { - case .clientIdleServerIdle: - break - case .clientOpenServerIdle: - // Open client - XCTAssertNoThrow(try stateMachine.receive(headers: clientMetadata, endStream: false)) - case .clientOpenServerOpen: - // Open client - XCTAssertNoThrow(try stateMachine.receive(headers: clientMetadata, endStream: false)) - // Open server - XCTAssertNoThrow(try stateMachine.send(metadata: Metadata(headers: .serverInitialMetadata))) - case .clientOpenServerClosed: - // Open client - XCTAssertNoThrow(try stateMachine.receive(headers: clientMetadata, endStream: false)) - // Open server - XCTAssertNoThrow(try stateMachine.send(metadata: Metadata(headers: .serverInitialMetadata))) - // Close server - XCTAssertNoThrow( - try stateMachine.send( - status: .init(code: .ok, message: ""), - metadata: [] - ) - ) - case .clientClosedServerIdle: - // Open client - XCTAssertNoThrow(try stateMachine.receive(headers: clientMetadata, endStream: false)) - // Close client - XCTAssertNoThrow(try stateMachine.receive(buffer: .init(), endStream: true)) - case .clientClosedServerOpen: - // Open client - XCTAssertNoThrow(try stateMachine.receive(headers: clientMetadata, endStream: false)) - // Open server - XCTAssertNoThrow(try stateMachine.send(metadata: Metadata(headers: .serverInitialMetadata))) - // Close client - XCTAssertNoThrow(try stateMachine.receive(buffer: .init(), endStream: true)) - case .clientClosedServerClosed: - // Open client - XCTAssertNoThrow(try stateMachine.receive(headers: clientMetadata, endStream: false)) - // Open server - XCTAssertNoThrow(try stateMachine.send(metadata: Metadata(headers: .serverInitialMetadata))) - // Close client - XCTAssertNoThrow(try stateMachine.receive(buffer: .init(), endStream: true)) - // Close server - XCTAssertNoThrow( - try stateMachine.send( - status: .init(code: .ok, message: ""), - metadata: [] - ) - ) - } - - return stateMachine - } - - // - MARK: Send Metadata - - func testSendMetadataWhenClientIdleAndServerIdle() throws { - var stateMachine = self.makeServerStateMachine(targetState: .clientIdleServerIdle) - - XCTAssertThrowsError( - ofType: GRPCStreamStateMachine.InvalidState.self, - try stateMachine.send(metadata: .init()) - ) { error in - XCTAssertEqual( - error.message, - "Client cannot be idle if server is sending initial metadata: it must have opened." - ) - } - } - - func testSendMetadataWhenClientOpenAndServerIdle() throws { - var stateMachine = self.makeServerStateMachine( - targetState: .clientOpenServerIdle, - deflateCompressionEnabled: false - ) - XCTAssertEqual( - try stateMachine.send(metadata: .init()), - [ - ":status": "200", - "content-type": "application/grpc", - ] - ) - } - - func testSendMetadataWhenClientOpenAndServerIdle_AndCompressionEnabled() { - // Enable deflate compression on server - var stateMachine = self.makeServerStateMachine( - targetState: .clientOpenServerIdle, - deflateCompressionEnabled: true - ) - - XCTAssertEqual( - try stateMachine.send(metadata: .init()), - [ - ":status": "200", - "content-type": "application/grpc", - "grpc-encoding": "deflate", - ] - ) - } - - func testSendMetadataWhenClientOpenAndServerOpen() throws { - var stateMachine = self.makeServerStateMachine(targetState: .clientOpenServerOpen) - - // Try sending metadata again: should throw - XCTAssertThrowsError( - ofType: GRPCStreamStateMachine.InvalidState.self, - try stateMachine.send(metadata: .init()) - ) { error in - XCTAssertEqual(error.message, "Server has already sent initial metadata.") - } - } - - func testSendMetadataWhenClientOpenAndServerClosed() throws { - var stateMachine = self.makeServerStateMachine(targetState: .clientOpenServerClosed) - - // Try sending metadata again: should throw - XCTAssertThrowsError( - ofType: GRPCStreamStateMachine.InvalidState.self, - try stateMachine.send(metadata: .init()) - ) { error in - XCTAssertEqual(error.message, "Server cannot send metadata if closed.") - } - } - - func testSendMetadataWhenClientClosedAndServerIdle() throws { - var stateMachine = self.makeServerStateMachine(targetState: .clientClosedServerIdle) - - // We should be allowed to send initial metadata if client is closed: - // client may be finished sending request but may still be awaiting response. - XCTAssertNoThrow(try stateMachine.send(metadata: .init())) - } - - func testSendMetadataWhenClientClosedAndServerOpen() throws { - var stateMachine = self.makeServerStateMachine(targetState: .clientClosedServerOpen) - - // Try sending metadata again: should throw - XCTAssertThrowsError( - ofType: GRPCStreamStateMachine.InvalidState.self, - try stateMachine.send(metadata: .init()) - ) { error in - XCTAssertEqual(error.message, "Server has already sent initial metadata.") - } - } - - func testSendMetadataWhenClientClosedAndServerClosed() throws { - var stateMachine = self.makeServerStateMachine(targetState: .clientClosedServerClosed) - - // Try sending metadata again: should throw - XCTAssertThrowsError( - ofType: GRPCStreamStateMachine.InvalidState.self, - try stateMachine.send(metadata: .init()) - ) { error in - XCTAssertEqual(error.message, "Server cannot send metadata if closed.") - } - } - - // - MARK: Send Message - - func testSendMessageWhenClientIdleAndServerIdle() { - var stateMachine = self.makeServerStateMachine(targetState: .clientIdleServerIdle) - - XCTAssertThrowsError( - ofType: GRPCStreamStateMachine.InvalidState.self, - try stateMachine.send(message: [], promise: nil) - ) { error in - XCTAssertEqual( - error.message, - "Server must have sent initial metadata before sending a message." - ) - } - } - - func testSendMessageWhenClientOpenAndServerIdle() { - var stateMachine = self.makeServerStateMachine(targetState: .clientOpenServerIdle) - - // Now send a message - XCTAssertThrowsError( - ofType: GRPCStreamStateMachine.InvalidState.self, - try stateMachine.send(message: [], promise: nil) - ) { error in - XCTAssertEqual( - error.message, - "Server must have sent initial metadata before sending a message." - ) - } - } - - func testSendMessageWhenClientOpenAndServerOpen() { - var stateMachine = self.makeServerStateMachine(targetState: .clientOpenServerOpen) - - // Now send a message - XCTAssertNoThrow(try stateMachine.send(message: [], promise: nil)) - } - - func testSendMessageWhenClientOpenAndServerClosed() { - var stateMachine = self.makeServerStateMachine(targetState: .clientOpenServerClosed) - - // Try sending another message: it should fail - XCTAssertThrowsError( - ofType: GRPCStreamStateMachine.InvalidState.self, - try stateMachine.send(message: [], promise: nil) - ) { error in - XCTAssertEqual(error.message, "Server can't send a message if it's closed.") - } - } - - func testSendMessageWhenClientClosedAndServerIdle() { - var stateMachine = self.makeServerStateMachine(targetState: .clientClosedServerIdle) - - XCTAssertThrowsError( - ofType: GRPCStreamStateMachine.InvalidState.self, - try stateMachine.send(message: [], promise: nil) - ) { error in - XCTAssertEqual( - error.message, - "Server must have sent initial metadata before sending a message." - ) - } - } - - func testSendMessageWhenClientClosedAndServerOpen() { - var stateMachine = self.makeServerStateMachine(targetState: .clientClosedServerOpen) - - // Try sending a message: even though client is closed, we should send it - // because it may be expecting a response. - XCTAssertNoThrow(try stateMachine.send(message: [], promise: nil)) - } - - func testSendMessageWhenClientClosedAndServerClosed() { - var stateMachine = self.makeServerStateMachine(targetState: .clientClosedServerClosed) - - // Try sending another message: it should fail - XCTAssertThrowsError( - ofType: GRPCStreamStateMachine.InvalidState.self, - try stateMachine.send(message: [], promise: nil) - ) { error in - XCTAssertEqual(error.message, "Server can't send a message if it's closed.") - } - } - - // - MARK: Send Status and Trailers - - func testSendStatusAndTrailersWhenClientIdle() { - var stateMachine = self.makeServerStateMachine(targetState: .clientIdleServerIdle) - - XCTAssertThrowsError( - ofType: GRPCStreamStateMachine.InvalidState.self, - try stateMachine.send( - status: .init(code: .ok, message: ""), - metadata: .init() - ) - ) { error in - XCTAssertEqual(error.message, "Server can't send status if client is idle.") - } - } - - func testSendStatusAndTrailersWhenClientOpenAndServerIdle() throws { - var stateMachine = self.makeServerStateMachine(targetState: .clientOpenServerIdle) - - let trailers = try stateMachine.send( - status: .init(code: .unknown, message: "RPC unknown"), - metadata: .init() - ) - - // Make sure it's a trailers-only response: it must have :status header and content-type - XCTAssertEqual( - trailers, - [ - ":status": "200", - "content-type": "application/grpc", - "grpc-status": "2", - "grpc-message": "RPC unknown", - ] - ) - - // Try sending another message: it should fail because server is now closed. - XCTAssertThrowsError( - ofType: GRPCStreamStateMachine.InvalidState.self, - try stateMachine.send(message: [], promise: nil) - ) { error in - XCTAssertEqual(error.message, "Server can't send a message if it's closed.") - } - } - - func testSendStatusAndTrailersWhenClientOpenAndServerOpen() throws { - var stateMachine = self.makeServerStateMachine(targetState: .clientOpenServerOpen) - - let trailers = try stateMachine.send( - status: .init(code: .ok, message: ""), - metadata: .init() - ) - - // Make sure it's NOT a trailers-only response, because the server was - // already open (so it sent initial metadata): it shouldn't have :status or content-type headers - XCTAssertEqual(trailers, ["grpc-status": "0"]) - - // Try sending another message: it should fail because server is now closed. - XCTAssertThrowsError( - ofType: GRPCStreamStateMachine.InvalidState.self, - try stateMachine.send(message: [], promise: nil) - ) { error in - XCTAssertEqual(error.message, "Server can't send a message if it's closed.") - } - } - - func testSendStatusAndTrailersWhenClientOpenAndServerClosed() { - var stateMachine = self.makeServerStateMachine(targetState: .clientOpenServerClosed) - - XCTAssertThrowsError( - ofType: GRPCStreamStateMachine.InvalidState.self, - try stateMachine.send( - status: .init(code: .ok, message: ""), - metadata: .init() - ) - ) { error in - XCTAssertEqual(error.message, "Server can't send anything if closed.") - } - } - - func testSendStatusAndTrailersWhenClientClosedAndServerIdle() throws { - var stateMachine = self.makeServerStateMachine(targetState: .clientClosedServerIdle) - - let trailers = try stateMachine.send( - status: .init(code: .unknown, message: "RPC unknown"), - metadata: .init() - ) - - // Make sure it's a trailers-only response: it must have :status header and content-type - XCTAssertEqual( - trailers, - [ - ":status": "200", - "content-type": "application/grpc", - "grpc-status": "2", - "grpc-message": "RPC unknown", - ] - ) - - // Try sending another message: it should fail because server is now closed. - XCTAssertThrowsError( - ofType: GRPCStreamStateMachine.InvalidState.self, - try stateMachine.send(message: [], promise: nil) - ) { error in - XCTAssertEqual(error.message, "Server can't send a message if it's closed.") - } - } - - func testSendStatusAndTrailersWhenClientClosedAndServerOpen() throws { - var stateMachine = self.makeServerStateMachine(targetState: .clientClosedServerOpen) - - let trailers = try stateMachine.send( - status: .init(code: .ok, message: ""), - metadata: .init() - ) - - // Make sure it's NOT a trailers-only response, because the server was - // already open (so it sent initial metadata): it shouldn't have :status or content-type headers - XCTAssertEqual(trailers, ["grpc-status": "0"]) - - // Try sending another message: it should fail because server is now closed. - XCTAssertThrowsError( - ofType: GRPCStreamStateMachine.InvalidState.self, - try stateMachine.send(message: [], promise: nil) - ) { error in - XCTAssertEqual(error.message, "Server can't send a message if it's closed.") - } - } - - func testSendStatusAndTrailersWhenClientClosedAndServerClosed() { - var stateMachine = self.makeServerStateMachine(targetState: .clientClosedServerClosed) - - XCTAssertThrowsError( - ofType: GRPCStreamStateMachine.InvalidState.self, - try stateMachine.send( - status: .init(code: .ok, message: ""), - metadata: .init() - ) - ) { error in - XCTAssertEqual(error.message, "Server can't send anything if closed.") - } - } - - // - MARK: Receive metadata - - func testReceiveMetadataWhenClientIdleAndServerIdle() throws { - var stateMachine = self.makeServerStateMachine(targetState: .clientIdleServerIdle) - - let action = try stateMachine.receive(headers: .clientInitialMetadata, endStream: false) - XCTAssertEqual( - action, - .receivedMetadata( - Metadata(headers: .clientInitialMetadata), - MethodDescriptor(path: "/test/test") - ) - ) - } - - func testReceiveMetadataWhenClientIdleAndServerIdle_WithEndStream() throws { - var stateMachine = self.makeServerStateMachine(targetState: .clientIdleServerIdle) - - let action = try stateMachine.receive(headers: .clientInitialMetadata, endStream: true) - XCTAssertEqual( - action, - .receivedMetadata( - Metadata(headers: .clientInitialMetadata), - MethodDescriptor(path: "/test/test") - ) - ) - } - - func testReceiveMetadataWhenClientIdleAndServerIdle_MissingContentType() throws { - var stateMachine = self.makeServerStateMachine(targetState: .clientIdleServerIdle) - - let action = try stateMachine.receive( - headers: .receivedWithoutContentType, - endStream: false - ) - - self.assertRejectedRPC(action) { trailers in - XCTAssertEqual(trailers.count, 1) - XCTAssertEqual(trailers.firstString(forKey: .status), "415") - } - } - - func testReceiveMetadataWhenClientIdleAndServerIdle_InvalidContentType() throws { - var stateMachine = self.makeServerStateMachine(targetState: .clientIdleServerIdle) - - let action = try stateMachine.receive( - headers: .receivedWithInvalidContentType, - endStream: false - ) - - self.assertRejectedRPC(action) { trailers in - XCTAssertEqual(trailers.count, 1) - XCTAssertEqual(trailers.firstString(forKey: .status), "415") - } - } - - func testReceiveMetadataWhenClientIdleAndServerIdle_MissingPath() throws { - var stateMachine = self.makeServerStateMachine(targetState: .clientIdleServerIdle) - - let action = try stateMachine.receive( - headers: .receivedWithoutEndpoint, - endStream: false - ) - - self.assertRejectedRPC(action) { trailers in - XCTAssertEqual( - trailers, - [ - ":status": "200", - "content-type": "application/grpc", - "grpc-status": String(Status.Code.invalidArgument.rawValue), - "grpc-message": "No :path header has been set.", - ] - ) - } - } - - func testReceiveMetadataWhenClientIdleAndServerIdle_InvalidPath() throws { - var stateMachine = self.makeServerStateMachine(targetState: .clientIdleServerIdle) - - let action = try stateMachine.receive( - headers: .receivedWithInvalidPath, - endStream: false - ) - - self.assertRejectedRPC(action) { trailers in - XCTAssertEqual( - trailers, - [ - ":status": "200", - "content-type": "application/grpc", - "grpc-status": String(Status.Code.unimplemented.rawValue), - "grpc-message": - "The given :path (someinvalidpath) does not correspond to a valid method.", - ] - ) - } - } - - func testReceiveMetadataWhenClientIdleAndServerIdle_MissingTE() throws { - var stateMachine = self.makeServerStateMachine(targetState: .clientIdleServerIdle) - - let action = try stateMachine.receive( - headers: .receivedWithoutTE, - endStream: false - ) - - let metadata: Metadata = [ - ":path": "/test/test", - ":scheme": "http", - ":method": "POST", - "content-type": "application/grpc", - ] - let descriptor = MethodDescriptor(service: "test", method: "test") - XCTAssertEqual(action, .receivedMetadata(metadata, descriptor)) - } - - func testReceiveMetadataWhenClientIdleAndServerIdle_MissingMethod() throws { - var stateMachine = self.makeServerStateMachine(targetState: .clientIdleServerIdle) - - let action = try stateMachine.receive( - headers: .receivedWithoutMethod, - endStream: false - ) - - self.assertRejectedRPC(action) { trailers in - XCTAssertEqual( - trailers, - [ - ":status": "200", - "content-type": "application/grpc", - "grpc-status": "3", - "grpc-message": - ":method header is expected to be present and have a value of \"POST\".", - ] - ) - } - } - - func testReceiveMetadataWhenClientIdleAndServerIdle_InvalidMethod() throws { - var stateMachine = self.makeServerStateMachine(targetState: .clientIdleServerIdle) - - let action = try stateMachine.receive( - headers: .receivedWithInvalidMethod, - endStream: false - ) - - self.assertRejectedRPC(action) { trailers in - XCTAssertEqual( - trailers, - [ - ":status": "200", - "content-type": "application/grpc", - "grpc-status": "3", - "grpc-message": - ":method header is expected to be present and have a value of \"POST\".", - ] - ) - } - } - - func testReceiveMetadataWhenClientIdleAndServerIdle_MissingScheme() throws { - var stateMachine = self.makeServerStateMachine(targetState: .clientIdleServerIdle) - - let action = try stateMachine.receive( - headers: .receivedWithoutScheme, - endStream: false - ) - - self.assertRejectedRPC(action) { trailers in - XCTAssertEqual( - trailers, - [ - ":status": "200", - "content-type": "application/grpc", - "grpc-status": "3", - "grpc-message": ":scheme header must be present and one of \"http\" or \"https\".", - ] - ) - } - } - - func testReceiveMetadataWhenClientIdleAndServerIdle_InvalidScheme() throws { - var stateMachine = self.makeServerStateMachine(targetState: .clientIdleServerIdle) - - let action = try stateMachine.receive( - headers: .receivedWithInvalidScheme, - endStream: false - ) - - self.assertRejectedRPC(action) { trailers in - XCTAssertEqual( - trailers, - [ - ":status": "200", - "content-type": "application/grpc", - "grpc-status": "3", - "grpc-message": ":scheme header must be present and one of \"http\" or \"https\".", - ] - ) - } - } - - func testReceiveMetadataWhenClientIdleAndServerIdle_ServerUnsupportedEncoding() throws { - var stateMachine = self.makeServerStateMachine( - targetState: .clientIdleServerIdle, - deflateCompressionEnabled: true - ) - - // Try opening client with a compression algorithm that is not accepted - // by the server. - let action = try stateMachine.receive( - headers: .clientInitialMetadataWithGzipCompression, - endStream: false - ) - - self.assertRejectedRPC(action) { trailers in - let expected: HPACKHeaders = [ - ":status": "200", - "content-type": "application/grpc", - "grpc-status": "12", - "grpc-message": - "gzip compression is not supported; supported algorithms are listed in grpc-accept-encoding", - "grpc-accept-encoding": "deflate", - "grpc-accept-encoding": "identity", - ] - XCTAssertEqual(expected.count, trailers.count, "Expected \(expected) but got \(trailers)") - for header in trailers { - XCTAssertTrue( - expected.contains { name, value, _ in - header.name == name && header.value == header.value - } - ) - } - } - } - - func testReceiveMetadataWhenClientOpenAndServerIdle() throws { - var stateMachine = self.makeServerStateMachine(targetState: .clientOpenServerIdle) - - // Try receiving initial metadata again - should be a protocol violation - let action = try stateMachine.receive(headers: .clientInitialMetadata, endStream: false) - XCTAssertEqual(action, .protocolViolation_serverOnly) - } - - func testReceiveMetadataWhenClientOpenAndServerOpen() throws { - var stateMachine = self.makeServerStateMachine(targetState: .clientOpenServerOpen) - - let action = try stateMachine.receive(headers: .clientInitialMetadata, endStream: false) - XCTAssertEqual(action, .protocolViolation_serverOnly) - } - - func testReceiveMetadataWhenClientOpenAndServerClosed() throws { - var stateMachine = self.makeServerStateMachine(targetState: .clientOpenServerClosed) - - let action = try stateMachine.receive(headers: .clientInitialMetadata, endStream: false) - XCTAssertEqual(action, .protocolViolation_serverOnly) - } - - func testReceiveMetadataWhenClientClosedAndServerIdle() { - var stateMachine = self.makeServerStateMachine(targetState: .clientClosedServerIdle) - - XCTAssertThrowsError( - ofType: GRPCStreamStateMachine.InvalidState.self, - try stateMachine.receive(headers: .clientInitialMetadata, endStream: false) - ) { error in - XCTAssertEqual(error.message, "Client can't have sent metadata if closed.") - } - } - - func testReceiveMetadataWhenClientClosedAndServerOpen() { - var stateMachine = self.makeServerStateMachine(targetState: .clientClosedServerOpen) - - XCTAssertThrowsError( - ofType: GRPCStreamStateMachine.InvalidState.self, - try stateMachine.receive(headers: .clientInitialMetadata, endStream: false) - ) { error in - XCTAssertEqual(error.message, "Client can't have sent metadata if closed.") - } - } - - func testReceiveMetadataWhenClientClosedAndServerClosed() { - var stateMachine = self.makeServerStateMachine(targetState: .clientClosedServerClosed) - - XCTAssertThrowsError( - ofType: GRPCStreamStateMachine.InvalidState.self, - try stateMachine.receive(headers: .clientInitialMetadata, endStream: false) - ) { error in - XCTAssertEqual(error.message, "Client can't have sent metadata if closed.") - } - } - - // - MARK: Receive message - - func testReceiveMessageWhenClientIdleAndServerIdle() { - var stateMachine = self.makeServerStateMachine(targetState: .clientIdleServerIdle) - - XCTAssertThrowsError( - ofType: GRPCStreamStateMachine.InvalidState.self, - try stateMachine.receive(buffer: .init(), endStream: false) - ) { error in - XCTAssertEqual(error.message, "Can't have received a message if client is idle.") - } - } - - func testReceiveMessageWhenClientOpenAndServerIdle() { - var stateMachine = self.makeServerStateMachine(targetState: .clientOpenServerIdle) - - // Receive messages successfully: the second one should close client. - XCTAssertNoThrow(try stateMachine.receive(buffer: .init(), endStream: false)) - XCTAssertNoThrow(try stateMachine.receive(buffer: .init(), endStream: true)) - - // Verify client is now closed - XCTAssertThrowsError( - ofType: GRPCStreamStateMachine.InvalidState.self, - try stateMachine.receive(buffer: .init(), endStream: false) - ) { error in - XCTAssertEqual(error.message, "Client can't send a message if closed.") - } - } - - func testReceiveMessageWhenClientOpenAndServerOpen() throws { - var stateMachine = self.makeServerStateMachine(targetState: .clientOpenServerOpen) - - // Receive messages successfully: the second one should close client. - XCTAssertNoThrow(try stateMachine.receive(buffer: .init(), endStream: false)) - XCTAssertNoThrow(try stateMachine.receive(buffer: .init(), endStream: true)) - - // Verify client is now closed - XCTAssertThrowsError( - ofType: GRPCStreamStateMachine.InvalidState.self, - try stateMachine.receive(buffer: .init(), endStream: false) - ) { error in - XCTAssertEqual(error.message, "Client can't send a message if closed.") - } - } - - func testReceiveMessage_ServerCompressionEnabled() throws { - // Enable deflate compression on server - var stateMachine = self.makeServerStateMachine( - targetState: .clientOpenServerOpen, - deflateCompressionEnabled: true - ) - - let originalMessage = [UInt8]([42, 42, 43, 43]) - - // Receiving uncompressed message should still work. - let receivedUncompressedBytes = try self.frameMessage(originalMessage, compression: .none) - XCTAssertNoThrow(try stateMachine.receive(buffer: receivedUncompressedBytes, endStream: false)) - var receivedAction = stateMachine.nextInboundMessage() - switch receivedAction { - case .noMoreMessages, .awaitMoreMessages: - XCTFail("Should have received message") - case .receiveMessage(let receivedMessaged): - XCTAssertEqual(originalMessage, receivedMessaged) - } - - // Receiving compressed message with deflate should work - let receivedDeflateCompressedBytes = try self.frameMessage( - originalMessage, - compression: .deflate - ) - XCTAssertNoThrow( - try stateMachine.receive(buffer: receivedDeflateCompressedBytes, endStream: false) - ) - receivedAction = stateMachine.nextInboundMessage() - switch receivedAction { - case .noMoreMessages, .awaitMoreMessages: - XCTFail("Should have received message") - case .receiveMessage(let receivedMessaged): - XCTAssertEqual(originalMessage, receivedMessaged) - } - - // Receiving compressed message with gzip (unsupported) should throw error - let receivedGZIPCompressedBytes = try self.frameMessage(originalMessage, compression: .gzip) - let action = try stateMachine.receive(buffer: receivedGZIPCompressedBytes, endStream: false) - XCTAssertEqual( - action, - .forwardErrorAndClose_serverOnly( - RPCError(code: .internalError, message: "Failed to decode message") - ) - ) - - receivedAction = stateMachine.nextInboundMessage() - switch receivedAction { - case .awaitMoreMessages: - () - case .noMoreMessages: - XCTFail("Should be awaiting for more messages") - case .receiveMessage: - XCTFail("Should not have received message") - } - } - - func testReceiveMessageWhenClientOpenAndServerClosed() { - var stateMachine = self.makeServerStateMachine(targetState: .clientOpenServerClosed) - - // Client is not done sending request, don't fail. - XCTAssertEqual(try stateMachine.receive(buffer: ByteBuffer(), endStream: false), .doNothing) - } - - func testReceiveMessageWhenClientClosedAndServerIdle() { - var stateMachine = self.makeServerStateMachine(targetState: .clientClosedServerIdle) - - XCTAssertThrowsError( - ofType: GRPCStreamStateMachine.InvalidState.self, - try stateMachine.receive(buffer: .init(), endStream: false) - ) { error in - XCTAssertEqual(error.message, "Client can't send a message if closed.") - } - } - - func testReceiveMessageWhenClientClosedAndServerOpen() { - var stateMachine = self.makeServerStateMachine(targetState: .clientClosedServerOpen) - - XCTAssertThrowsError( - ofType: GRPCStreamStateMachine.InvalidState.self, - try stateMachine.receive(buffer: .init(), endStream: false) - ) { error in - XCTAssertEqual(error.message, "Client can't send a message if closed.") - } - } - - func testReceiveMessageWhenClientClosedAndServerClosed() { - var stateMachine = self.makeServerStateMachine(targetState: .clientClosedServerClosed) - - XCTAssertThrowsError( - ofType: GRPCStreamStateMachine.InvalidState.self, - try stateMachine.receive(buffer: .init(), endStream: false) - ) { error in - XCTAssertEqual(error.message, "Client can't send a message if closed.") - } - } - - // - MARK: Next outbound message - - func testNextOutboundMessageWhenClientIdleAndServerIdle() { - var stateMachine = self.makeServerStateMachine(targetState: .clientIdleServerIdle) - - XCTAssertThrowsError( - ofType: GRPCStreamStateMachine.InvalidState.self, - try stateMachine.nextOutboundFrame() - ) { error in - XCTAssertEqual(error.message, "Server is not open yet.") - } - } - - func testNextOutboundMessageWhenClientOpenAndServerIdle() throws { - var stateMachine = self.makeServerStateMachine(targetState: .clientOpenServerIdle) - - XCTAssertThrowsError( - ofType: GRPCStreamStateMachine.InvalidState.self, - try stateMachine.nextOutboundFrame() - ) { error in - XCTAssertEqual(error.message, "Server is not open yet.") - } - } - - func testNextOutboundMessageWhenClientOpenAndServerIdle_WithCompression() throws { - var stateMachine = self.makeServerStateMachine(targetState: .clientOpenServerIdle) - - XCTAssertThrowsError( - ofType: GRPCStreamStateMachine.InvalidState.self, - try stateMachine.nextOutboundFrame() - ) { error in - XCTAssertEqual(error.message, "Server is not open yet.") - } - } - - func testNextOutboundMessageWhenClientOpenAndServerOpen() throws { - var stateMachine = self.makeServerStateMachine(targetState: .clientOpenServerOpen) - - XCTAssertEqual(try stateMachine.nextOutboundFrame(), .awaitMoreMessages) - - XCTAssertNoThrow(try stateMachine.send(message: [42, 42], promise: nil)) - - let response = try stateMachine.nextOutboundFrame() - let expectedBytes: [UInt8] = [ - 0, // compression flag: unset - 0, 0, 0, 2, // message length: 2 bytes - 42, 42, // original message - ] - XCTAssertEqual(response, .sendFrame(frame: ByteBuffer(bytes: expectedBytes), promise: nil)) - - // And then make sure that nothing else is returned - XCTAssertEqual(try stateMachine.nextOutboundFrame(), .awaitMoreMessages) - } - - func testNextOutboundMessageWhenClientOpenAndServerOpen_WithCompression() throws { - var stateMachine = self.makeServerStateMachine( - targetState: .clientOpenServerOpen, - deflateCompressionEnabled: true - ) - - XCTAssertEqual(try stateMachine.nextOutboundFrame(), .awaitMoreMessages) - - let originalMessage = [UInt8]([42, 42, 43, 43]) - XCTAssertNoThrow(try stateMachine.send(message: originalMessage, promise: nil)) - - let response = try stateMachine.nextOutboundFrame() - let framedMessage = try self.frameMessage(originalMessage, compression: .deflate) - XCTAssertEqual(response, .sendFrame(frame: framedMessage, promise: nil)) - } - - func testNextOutboundMessageWhenClientOpenAndServerClosed() throws { - var stateMachine = self.makeServerStateMachine(targetState: .clientOpenServerOpen) - - // Send message and close server - XCTAssertNoThrow(try stateMachine.send(message: [42, 42], promise: nil)) - XCTAssertNoThrow( - try stateMachine.send( - status: .init(code: .ok, message: ""), - metadata: [] - ) - ) - - let response = try stateMachine.nextOutboundFrame() - let expectedBytes: [UInt8] = [ - 0, // compression flag: unset - 0, 0, 0, 2, // message length: 2 bytes - 42, 42, // original message - ] - XCTAssertEqual(response, .sendFrame(frame: ByteBuffer(bytes: expectedBytes), promise: nil)) - - // And then make sure that nothing else is returned anymore - XCTAssertEqual(try stateMachine.nextOutboundFrame(), .noMoreMessages) - } - - func testNextOutboundMessageWhenClientClosedAndServerIdle() throws { - var stateMachine = self.makeServerStateMachine(targetState: .clientClosedServerIdle) - - XCTAssertThrowsError( - ofType: GRPCStreamStateMachine.InvalidState.self, - try stateMachine.nextOutboundFrame() - ) { error in - XCTAssertEqual(error.message, "Server is not open yet.") - } - } - - func testNextOutboundMessageWhenClientClosedAndServerOpen() throws { - var stateMachine = self.makeServerStateMachine(targetState: .clientOpenServerOpen) - - // Send a message - XCTAssertNoThrow(try stateMachine.send(message: [42, 42], promise: nil)) - - // Close client - XCTAssertNoThrow(try stateMachine.receive(buffer: .init(), endStream: true)) - - // Send another message - XCTAssertNoThrow(try stateMachine.send(message: [43, 43], promise: nil)) - - // Make sure that getting the next outbound message _does_ return the message - // we have enqueued. - let response = try stateMachine.nextOutboundFrame() - let expectedBytes: [UInt8] = [ - 0, // compression flag: unset - 0, 0, 0, 2, // message length: 2 bytes - 42, 42, // original message - // End of first message - beginning of second - 0, // compression flag: unset - 0, 0, 0, 2, // message length: 2 bytes - 43, 43, // original message - ] - XCTAssertEqual(response, .sendFrame(frame: ByteBuffer(bytes: expectedBytes), promise: nil)) - - // And then make sure that nothing else is returned anymore - XCTAssertEqual(try stateMachine.nextOutboundFrame(), .awaitMoreMessages) - } - - func testNextOutboundMessageWhenClientClosedAndServerClosed() throws { - var stateMachine = self.makeServerStateMachine(targetState: .clientClosedServerOpen) - - // Send a message and close server - XCTAssertNoThrow(try stateMachine.send(message: [42, 42], promise: nil)) - XCTAssertNoThrow( - try stateMachine.send( - status: .init(code: .ok, message: ""), - metadata: [] - ) - ) - - // We have enqueued a message, make sure we return it even though server is closed, - // because we haven't yet drained all of the pending messages. - let response = try stateMachine.nextOutboundFrame() - let expectedBytes: [UInt8] = [ - 0, // compression flag: unset - 0, 0, 0, 2, // message length: 2 bytes - 42, 42, // original message - ] - XCTAssertEqual(response, .sendFrame(frame: ByteBuffer(bytes: expectedBytes), promise: nil)) - - // And then make sure that nothing else is returned anymore - XCTAssertEqual(try stateMachine.nextOutboundFrame(), .noMoreMessages) - } - - // - MARK: Next inbound message - - func testNextInboundMessageWhenClientIdleAndServerIdle() { - var stateMachine = self.makeServerStateMachine(targetState: .clientIdleServerIdle) - XCTAssertEqual(stateMachine.nextInboundMessage(), .awaitMoreMessages) - } - - func testNextInboundMessageWhenClientOpenAndServerIdle() { - var stateMachine = self.makeServerStateMachine(targetState: .clientOpenServerIdle) - XCTAssertEqual(stateMachine.nextInboundMessage(), .awaitMoreMessages) - } - - func testNextInboundMessageWhenClientOpenAndServerOpen() throws { - var stateMachine = self.makeServerStateMachine(targetState: .clientOpenServerOpen) - - let receivedBytes = ByteBuffer(bytes: [ - 0, // compression flag: unset - 0, 0, 0, 2, // message length: 2 bytes - 42, 42, // original message - ]) - XCTAssertEqual( - try stateMachine.receive(buffer: receivedBytes, endStream: false), - .readInbound - ) - - XCTAssertEqual(stateMachine.nextInboundMessage(), .receiveMessage([42, 42])) - XCTAssertEqual(stateMachine.nextInboundMessage(), .awaitMoreMessages) - } - - func testNextInboundMessageWhenClientOpenAndServerOpen_WithCompression() throws { - var stateMachine = self.makeServerStateMachine( - targetState: .clientOpenServerOpen, - deflateCompressionEnabled: true - ) - - let originalMessage = [UInt8]([42, 42, 43, 43]) - let receivedBytes = try self.frameMessage(originalMessage, compression: .deflate) - - XCTAssertEqual( - try stateMachine.receive(buffer: receivedBytes, endStream: false), - .readInbound - ) - - XCTAssertEqual(stateMachine.nextInboundMessage(), .receiveMessage(originalMessage)) - XCTAssertEqual(stateMachine.nextInboundMessage(), .awaitMoreMessages) - } - - func testNextInboundMessageWhenClientOpenAndServerClosed() throws { - var stateMachine = self.makeServerStateMachine(targetState: .clientOpenServerOpen) - - let receivedBytes = ByteBuffer(bytes: [ - 0, // compression flag: unset - 0, 0, 0, 2, // message length: 2 bytes - 42, 42, // original message - ]) - XCTAssertEqual( - try stateMachine.receive(buffer: receivedBytes, endStream: false), - .readInbound - ) - - // Close server - XCTAssertNoThrow( - try stateMachine.send( - status: .init(code: .ok, message: ""), - metadata: [] - ) - ) - - XCTAssertEqual(stateMachine.nextInboundMessage(), .noMoreMessages) - } - - func testNextInboundMessageWhenClientClosedAndServerIdle() throws { - var stateMachine = self.makeServerStateMachine(targetState: .clientOpenServerIdle) - let action = try stateMachine.receive( - buffer: ByteBuffer(repeating: 0, count: 5), - endStream: true - ) - XCTAssertEqual(action, .readInbound) - XCTAssertEqual(stateMachine.nextInboundMessage(), .receiveMessage([])) - XCTAssertEqual(stateMachine.nextInboundMessage(), .noMoreMessages) - } - - func testNextInboundMessageWhenClientClosedAndServerOpen() throws { - var stateMachine = self.makeServerStateMachine(targetState: .clientOpenServerOpen) - - let receivedBytes = ByteBuffer(bytes: [ - 0, // compression flag: unset - 0, 0, 0, 2, // message length: 2 bytes - 42, 42, // original message - ]) - XCTAssertEqual( - try stateMachine.receive(buffer: receivedBytes, endStream: false), - .readInbound - ) - - // Close client - XCTAssertNoThrow(try stateMachine.receive(buffer: .init(), endStream: true)) - - // Even though the client is closed, because the server received a message - // while it was still open, we must get the message now. - XCTAssertEqual(stateMachine.nextInboundMessage(), .receiveMessage([42, 42])) - XCTAssertEqual(stateMachine.nextInboundMessage(), .noMoreMessages) - } - - func testNextInboundMessageWhenClientClosedAndServerClosed() throws { - var stateMachine = self.makeServerStateMachine(targetState: .clientOpenServerOpen) - - let receivedBytes = ByteBuffer(bytes: [ - 0, // compression flag: unset - 0, 0, 0, 2, // message length: 2 bytes - 42, 42, // original message - ]) - XCTAssertEqual( - try stateMachine.receive(buffer: receivedBytes, endStream: false), - .readInbound - ) - - // Close server - XCTAssertNoThrow( - try stateMachine.send( - status: .init(code: .ok, message: ""), - metadata: [] - ) - ) - - // Close client - XCTAssertNoThrow(try stateMachine.receive(buffer: .init(), endStream: true)) - - // The server is closed, the message should be dropped. - XCTAssertEqual(stateMachine.nextInboundMessage(), .noMoreMessages) - } - - // - MARK: Unexpected close - - func testUnexpectedCloseWhenClientIdleOrOpen() throws { - let reasonAndExpectedErrorPairs = [ - ( - GRPCStreamStateMachine.UnexpectedInboundCloseReason.channelInactive, - RPCError(code: .unavailable, message: "Stream unexpectedly closed.") - ), - ( - GRPCStreamStateMachine.UnexpectedInboundCloseReason.streamReset, - RPCError( - code: .unavailable, - message: "Stream unexpectedly closed: a RST_STREAM frame was received." - ) - ), - ( - GRPCStreamStateMachine.UnexpectedInboundCloseReason.errorThrown( - RPCError(code: .deadlineExceeded, message: "Test error") - ), - RPCError(code: .deadlineExceeded, message: "Test error") - ), - ] - let states = [ - TargetStateMachineState.clientIdleServerIdle, - .clientOpenServerIdle, - .clientOpenServerOpen, - .clientOpenServerClosed, - ] - - for state in states { - for (closeReason, expectedError) in reasonAndExpectedErrorPairs { - var stateMachine = self.makeServerStateMachine(targetState: state) - var action = stateMachine.unexpectedInboundClose(reason: closeReason) - guard case .fireError_serverOnly(let error) = action else { - XCTFail("Should have been `fireError` but was `\(action)` (state: \(state)).") - return - } - XCTAssertEqual(error as? RPCError, expectedError) - - // Calling unexpectedInboundClose again should return `doNothing` because - // we're already closed. - action = stateMachine.unexpectedInboundClose(reason: closeReason) - guard case .doNothing = action else { - XCTFail("Should have been `doNothing` but was `\(action)` (state: \(state)).") - return - } - } - } - } - - func testUnexpectedCloseWhenClientClosed() throws { - let closeReasons = [ - GRPCStreamStateMachine.UnexpectedInboundCloseReason.channelInactive, - .streamReset, - .errorThrown(RPCError(code: .deadlineExceeded, message: "Test error")), - ] - let states = [ - TargetStateMachineState.clientClosedServerIdle, - .clientClosedServerOpen, - .clientClosedServerClosed, - ] - - for state in states { - for closeReason in closeReasons { - var stateMachine = self.makeServerStateMachine(targetState: state) - var action = stateMachine.unexpectedInboundClose(reason: closeReason) - guard case .doNothing = action else { - XCTFail("Should have been `doNothing` but was `\(action)` (state: \(state)).") - return - } - - // Calling unexpectedInboundClose again should return `doNothing` again. - action = stateMachine.unexpectedInboundClose(reason: closeReason) - guard case .doNothing = action else { - XCTFail("Should have been `doNothing` but was `\(action)` (state: \(state)).") - return - } - } - } - } - - // - MARK: Common paths - - func testNormalFlow() throws { - var stateMachine = self.makeServerStateMachine(targetState: .clientIdleServerIdle) - - // Client sends metadata - let receiveMetadataAction = try stateMachine.receive( - headers: .clientInitialMetadata, - endStream: false - ) - XCTAssertEqual( - receiveMetadataAction, - .receivedMetadata( - Metadata(headers: .clientInitialMetadata), - MethodDescriptor(path: "/test/test") - ) - ) - - // Server sends initial metadata - let sentInitialHeaders = try stateMachine.send(metadata: Metadata(headers: ["custom": "value"])) - XCTAssertEqual( - sentInitialHeaders, - [ - ":status": "200", - "content-type": "application/grpc", - "custom": "value", - ] - ) - - // Client sends messages - let deframedMessage = [UInt8]([1, 2, 3, 4]) - let completeMessage = try self.frameMessage(deframedMessage, compression: .none) - // Split message into two parts to make sure the stitching together of the frames works well - let firstMessage = completeMessage.getSlice(at: 0, length: 4)! - let secondMessage = completeMessage.getSlice(at: 4, length: completeMessage.readableBytes - 4)! - - XCTAssertEqual( - try stateMachine.receive(buffer: firstMessage, endStream: false), - .readInbound - ) - XCTAssertEqual(stateMachine.nextInboundMessage(), .awaitMoreMessages) - XCTAssertEqual( - try stateMachine.receive(buffer: secondMessage, endStream: false), - .readInbound - ) - XCTAssertEqual(stateMachine.nextInboundMessage(), .receiveMessage(deframedMessage)) - - // Server sends response - let eventLoop = EmbeddedEventLoop() - let firstPromise = eventLoop.makePromise(of: Void.self) - let secondPromise = eventLoop.makePromise(of: Void.self) - - let firstResponse = [UInt8]([5, 6, 7]) - let secondResponse = [UInt8]([8, 9, 10]) - XCTAssertEqual(try stateMachine.nextOutboundFrame(), .awaitMoreMessages) - - try stateMachine.send(message: firstResponse, promise: firstPromise) - try stateMachine.send(message: secondResponse, promise: secondPromise) - - // Make sure messages are outbound - let framedMessages = try self.frameMessages( - [firstResponse, secondResponse], - compression: .none - ) - - guard - case .sendFrame(let nextOutboundByteBuffer, let nextOutboundPromise) = - try stateMachine.nextOutboundFrame() - else { - XCTFail("Should have received .sendMessage") - return - } - XCTAssertEqual(nextOutboundByteBuffer, framedMessages) - XCTAssertTrue(firstPromise.futureResult === nextOutboundPromise?.futureResult) - - // Make sure that the promises associated with each sent message are chained - // together: when succeeding the one returned by the state machine on - // `nextOutboundMessage()`, the others should also be succeeded. - firstPromise.succeed() - try secondPromise.futureResult.assertSuccess().wait() - - // Client sends end - XCTAssertEqual( - try stateMachine.receive(buffer: ByteBuffer(), endStream: true), - .readInbound - ) - - // Server ends - let response = try stateMachine.send( - status: .init(code: .ok, message: ""), - metadata: [] - ) - XCTAssertEqual(response, ["grpc-status": "0"]) - - XCTAssertEqual(try stateMachine.nextOutboundFrame(), .noMoreMessages) - XCTAssertEqual(stateMachine.nextInboundMessage(), .noMoreMessages) - } - - func testClientClosesBeforeServerOpens() throws { - var stateMachine = self.makeServerStateMachine(targetState: .clientIdleServerIdle) - - // Client sends metadata - let receiveMetadataAction = try stateMachine.receive( - headers: .clientInitialMetadata, - endStream: false - ) - XCTAssertEqual( - receiveMetadataAction, - .receivedMetadata( - Metadata(headers: .clientInitialMetadata), - MethodDescriptor(path: "/test/test") - ) - ) - - // Client sends messages - let deframedMessage = [UInt8]([1, 2, 3, 4]) - let completeMessage = try self.frameMessage(deframedMessage, compression: .none) - // Split message into two parts to make sure the stitching together of the frames works well - let firstMessage = completeMessage.getSlice(at: 0, length: 4)! - let secondMessage = completeMessage.getSlice(at: 4, length: completeMessage.readableBytes - 4)! - - XCTAssertEqual( - try stateMachine.receive(buffer: firstMessage, endStream: false), - .readInbound - ) - XCTAssertEqual(stateMachine.nextInboundMessage(), .awaitMoreMessages) - XCTAssertEqual( - try stateMachine.receive(buffer: secondMessage, endStream: false), - .readInbound - ) - XCTAssertEqual(stateMachine.nextInboundMessage(), .receiveMessage(deframedMessage)) - - // Client sends end - XCTAssertEqual( - try stateMachine.receive(buffer: ByteBuffer(), endStream: true), - .readInbound - ) - - // Server sends initial metadata - let sentInitialHeaders = try stateMachine.send(metadata: Metadata(headers: ["custom": "value"])) - XCTAssertEqual( - sentInitialHeaders, - [ - "custom": "value", - ":status": "200", - "content-type": "application/grpc", - ] - ) - - // Server sends response - let firstResponse = [UInt8]([5, 6, 7]) - let secondResponse = [UInt8]([8, 9, 10]) - XCTAssertEqual(try stateMachine.nextOutboundFrame(), .awaitMoreMessages) - try stateMachine.send(message: firstResponse, promise: nil) - try stateMachine.send(message: secondResponse, promise: nil) - - // Make sure messages are outbound - let framedMessages = try self.frameMessages( - [firstResponse, secondResponse], - compression: .none - ) - XCTAssertEqual( - try stateMachine.nextOutboundFrame(), - .sendFrame(frame: framedMessages, promise: nil) - ) - - // Server ends - let response = try stateMachine.send( - status: .init(code: .ok, message: ""), - metadata: [] - ) - XCTAssertEqual(response, ["grpc-status": "0"]) - - XCTAssertEqual(try stateMachine.nextOutboundFrame(), .noMoreMessages) - XCTAssertEqual(stateMachine.nextInboundMessage(), .noMoreMessages) - } - - func testClientClosesBeforeServerResponds() throws { - var stateMachine = self.makeServerStateMachine(targetState: .clientIdleServerIdle) - - // Client sends metadata - let receiveMetadataAction = try stateMachine.receive( - headers: .clientInitialMetadata, - endStream: false - ) - XCTAssertEqual( - receiveMetadataAction, - .receivedMetadata( - Metadata(headers: .clientInitialMetadata), - MethodDescriptor(path: "/test/test") - ) - ) - - // Client sends messages - let deframedMessage = [UInt8]([1, 2, 3, 4]) - let completeMessage = try self.frameMessage(deframedMessage, compression: .none) - // Split message into two parts to make sure the stitching together of the frames works well - let firstMessage = completeMessage.getSlice(at: 0, length: 4)! - let secondMessage = completeMessage.getSlice(at: 4, length: completeMessage.readableBytes - 4)! - - XCTAssertEqual( - try stateMachine.receive(buffer: firstMessage, endStream: false), - .readInbound - ) - XCTAssertEqual(stateMachine.nextInboundMessage(), .awaitMoreMessages) - XCTAssertEqual( - try stateMachine.receive(buffer: secondMessage, endStream: false), - .readInbound - ) - XCTAssertEqual(stateMachine.nextInboundMessage(), .receiveMessage(deframedMessage)) - - // Server sends initial metadata - let sentInitialHeaders = try stateMachine.send(metadata: Metadata(headers: ["custom": "value"])) - XCTAssertEqual( - sentInitialHeaders, - [ - "custom": "value", - ":status": "200", - "content-type": "application/grpc", - ] - ) - - // Client sends end - XCTAssertEqual( - try stateMachine.receive(buffer: ByteBuffer(), endStream: true), - .readInbound - ) - - // Server sends response - let firstResponse = [UInt8]([5, 6, 7]) - let secondResponse = [UInt8]([8, 9, 10]) - XCTAssertEqual(try stateMachine.nextOutboundFrame(), .awaitMoreMessages) - try stateMachine.send(message: firstResponse, promise: nil) - try stateMachine.send(message: secondResponse, promise: nil) - - // Make sure messages are outbound - let framedMessages = try self.frameMessages( - [firstResponse, secondResponse], - compression: .none - ) - XCTAssertEqual( - try stateMachine.nextOutboundFrame(), - .sendFrame(frame: framedMessages, promise: nil) - ) - - // Server ends - let response = try stateMachine.send( - status: .init(code: .ok, message: ""), - metadata: [] - ) - XCTAssertEqual(response, ["grpc-status": "0"]) - - XCTAssertEqual(try stateMachine.nextOutboundFrame(), .noMoreMessages) - XCTAssertEqual(stateMachine.nextInboundMessage(), .noMoreMessages) - } -} - -extension XCTestCase { - @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) - func assertRejectedRPC( - _ action: GRPCStreamStateMachine.OnMetadataReceived, - expression: (HPACKHeaders) throws -> Void - ) rethrows { - guard case .rejectRPC_serverOnly(let trailers) = action else { - XCTFail("RPC should have been rejected.") - return - } - try expression(trailers) - } - - func frameMessage(_ message: [UInt8], compression: CompressionAlgorithm) throws -> ByteBuffer { - try frameMessages([message], compression: compression) - } - - func frameMessages(_ messages: [[UInt8]], compression: CompressionAlgorithm) throws -> ByteBuffer - { - var framer = GRPCMessageFramer() - let compressor: Zlib.Compressor? = { - switch compression { - case .deflate: - return Zlib.Compressor(method: .deflate) - case .gzip: - return Zlib.Compressor(method: .gzip) - default: - return nil - } - }() - defer { compressor?.end() } - for message in messages { - framer.append(message, promise: nil) - } - return try XCTUnwrap(framer.next(compressor: compressor)).bytes - } -} - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -extension GRPCStreamStateMachine.OnNextOutboundFrame { - public static func == ( - lhs: GRPCStreamStateMachine.OnNextOutboundFrame, - rhs: GRPCStreamStateMachine.OnNextOutboundFrame - ) -> Bool { - switch (lhs, rhs) { - case (.noMoreMessages, .noMoreMessages): - return true - case (.awaitMoreMessages, .awaitMoreMessages): - return true - case (.sendFrame(let lhsMessage, _), .sendFrame(let rhsMessage, _)): - // Note that we're not comparing the EventLoopPromises here, as they're - // not Equatable. This is fine though, since we only use this in tests. - return lhsMessage == rhsMessage - default: - return false - } - } -} - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -extension GRPCStreamStateMachine.OnNextOutboundFrame: Equatable {} diff --git a/Tests/GRPCHTTP2CoreTests/Internal/GRPCStatusMessageMarshallerTests.swift b/Tests/GRPCHTTP2CoreTests/Internal/GRPCStatusMessageMarshallerTests.swift deleted file mode 100644 index ac659fad9..000000000 --- a/Tests/GRPCHTTP2CoreTests/Internal/GRPCStatusMessageMarshallerTests.swift +++ /dev/null @@ -1,44 +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 XCTest - -@testable import GRPCHTTP2Core - -class GRPCStatusMessageMarshallerTests: XCTestCase { - func testASCIIMarshallingAndUnmarshalling() { - XCTAssertEqual(GRPCStatusMessageMarshaller.marshall("Hello, World!"), "Hello, World!") - XCTAssertEqual(GRPCStatusMessageMarshaller.unmarshall("Hello, World!"), "Hello, World!") - } - - func testPercentMarshallingAndUnmarshalling() { - XCTAssertEqual(GRPCStatusMessageMarshaller.marshall("%"), "%25") - XCTAssertEqual(GRPCStatusMessageMarshaller.unmarshall("%25"), "%") - - XCTAssertEqual(GRPCStatusMessageMarshaller.marshall("25%"), "25%25") - XCTAssertEqual(GRPCStatusMessageMarshaller.unmarshall("25%25"), "25%") - } - - func testUnicodeMarshalling() { - XCTAssertEqual(GRPCStatusMessageMarshaller.marshall("🚀"), "%F0%9F%9A%80") - XCTAssertEqual(GRPCStatusMessageMarshaller.unmarshall("%F0%9F%9A%80"), "🚀") - - let message = "\t\ntest with whitespace\r\nand Unicode BMP ☺ and non-BMP 😈\t\n" - let marshalled = - "%09%0Atest with whitespace%0D%0Aand Unicode BMP %E2%98%BA and non-BMP %F0%9F%98%88%09%0A" - XCTAssertEqual(GRPCStatusMessageMarshaller.marshall(message), marshalled) - XCTAssertEqual(GRPCStatusMessageMarshaller.unmarshall(marshalled), message) - } -} diff --git a/Tests/GRPCHTTP2CoreTests/Internal/ProcessUniqueIDTests.swift b/Tests/GRPCHTTP2CoreTests/Internal/ProcessUniqueIDTests.swift deleted file mode 100644 index 180bb030f..000000000 --- a/Tests/GRPCHTTP2CoreTests/Internal/ProcessUniqueIDTests.swift +++ /dev/null @@ -1,58 +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 XCTest - -@testable import GRPCHTTP2Core - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -final class ProcessUniqueIDTests: XCTestCase { - func testProcessUniqueIDIsUnique() { - var ids: Set = [] - for _ in 1 ... 100 { - let (inserted, _) = ids.insert(ProcessUniqueID()) - XCTAssertTrue(inserted) - } - - XCTAssertEqual(ids.count, 100) - } - - func testProcessUniqueIDDescription() { - let id = ProcessUniqueID() - let description = String(describing: id) - // We can't verify the exact description as we don't know what value to expect, we only - // know that it'll be an integer. - XCTAssertNotNil(UInt64(description)) - } - - func testSubchannelIDDescription() { - let id = SubchannelID() - let description = String(describing: id) - XCTAssert(description.hasPrefix("subchan_")) - } - - func testLoadBalancerIDDescription() { - let id = LoadBalancerID() - let description = String(describing: id) - XCTAssert(description.hasPrefix("lb_")) - } - - func testQueueEntryDescription() { - let id = QueueEntryID() - let description = String(describing: id) - XCTAssert(description.hasPrefix("q_entry_")) - } -} diff --git a/Tests/GRPCHTTP2CoreTests/Internal/TimerTests.swift b/Tests/GRPCHTTP2CoreTests/Internal/TimerTests.swift deleted file mode 100644 index eb920976c..000000000 --- a/Tests/GRPCHTTP2CoreTests/Internal/TimerTests.swift +++ /dev/null @@ -1,98 +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 NIOEmbedded -import Synchronization -import XCTest - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -internal final class TimerTests: XCTestCase { - func testScheduleOneOffTimer() { - let loop = EmbeddedEventLoop() - defer { try! loop.close() } - - let value = Atomic(0) - var timer = Timer(delay: .seconds(1), repeat: false) - timer.schedule(on: loop) { - let (old, _) = value.add(1, ordering: .releasing) - XCTAssertEqual(old, 0) - } - - loop.advanceTime(by: .milliseconds(999)) - XCTAssertEqual(value.load(ordering: .acquiring), 0) - loop.advanceTime(by: .milliseconds(1)) - XCTAssertEqual(value.load(ordering: .acquiring), 1) - - // Run again to make sure the task wasn't repeated. - loop.advanceTime(by: .seconds(1)) - XCTAssertEqual(value.load(ordering: .acquiring), 1) - } - - func testCancelOneOffTimer() { - let loop = EmbeddedEventLoop() - defer { try! loop.close() } - - var timer = Timer(delay: .seconds(1), repeat: false) - timer.schedule(on: loop) { - XCTFail("Timer wasn't cancelled") - } - - loop.advanceTime(by: .milliseconds(999)) - timer.cancel() - loop.advanceTime(by: .milliseconds(1)) - } - - func testScheduleRepeatedTimer() throws { - let loop = EmbeddedEventLoop() - defer { try! loop.close() } - - let counter = AtomicCounter() - var timer = Timer(delay: .seconds(1), repeat: true) - timer.schedule(on: loop) { - counter.increment() - } - - loop.advanceTime(by: .milliseconds(999)) - XCTAssertEqual(counter.value, 0) - loop.advanceTime(by: .milliseconds(1)) - XCTAssertEqual(counter.value, 1) - - loop.advanceTime(by: .seconds(1)) - XCTAssertEqual(counter.value, 2) - loop.advanceTime(by: .seconds(1)) - XCTAssertEqual(counter.value, 3) - - timer.cancel() - loop.advanceTime(by: .seconds(1)) - XCTAssertEqual(counter.value, 3) - } - - func testCancelRepeatedTimer() { - let loop = EmbeddedEventLoop() - defer { try! loop.close() } - - var timer = Timer(delay: .seconds(1), repeat: true) - timer.schedule(on: loop) { - XCTFail("Timer wasn't cancelled") - } - - loop.advanceTime(by: .milliseconds(999)) - timer.cancel() - loop.advanceTime(by: .milliseconds(1)) - } -} diff --git a/Tests/GRPCHTTP2CoreTests/OneOrManyQueueTests.swift b/Tests/GRPCHTTP2CoreTests/OneOrManyQueueTests.swift deleted file mode 100644 index 7f1fbf9f5..000000000 --- a/Tests/GRPCHTTP2CoreTests/OneOrManyQueueTests.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 XCTest - -@testable import GRPCHTTP2Core - -internal final class OneOrManyQueueTests: XCTestCase { - func testIsEmpty() { - XCTAssertTrue(OneOrManyQueue().isEmpty) - } - - func testIsEmptyManyBacked() { - XCTAssertTrue(OneOrManyQueue.manyBacked.isEmpty) - } - - func testCount() { - var queue = OneOrManyQueue() - XCTAssertEqual(queue.count, 0) - queue.append(1) - XCTAssertEqual(queue.count, 1) - } - - func testCountManyBacked() { - var manyBacked = OneOrManyQueue.manyBacked - XCTAssertEqual(manyBacked.count, 0) - for i in 1 ... 100 { - manyBacked.append(1) - XCTAssertEqual(manyBacked.count, i) - } - } - - func testAppendAndPop() { - var queue = OneOrManyQueue() - XCTAssertNil(queue.pop()) - - queue.append(1) - XCTAssertEqual(queue.count, 1) - XCTAssertEqual(queue.pop(), 1) - - XCTAssertNil(queue.pop()) - XCTAssertEqual(queue.count, 0) - XCTAssertTrue(queue.isEmpty) - } - - func testAppendAndPopManyBacked() { - var manyBacked = OneOrManyQueue.manyBacked - XCTAssertNil(manyBacked.pop()) - - manyBacked.append(1) - XCTAssertEqual(manyBacked.count, 1) - manyBacked.append(2) - XCTAssertEqual(manyBacked.count, 2) - - XCTAssertEqual(manyBacked.pop(), 1) - XCTAssertEqual(manyBacked.count, 1) - - XCTAssertEqual(manyBacked.pop(), 2) - XCTAssertEqual(manyBacked.count, 0) - - XCTAssertNil(manyBacked.pop()) - XCTAssertTrue(manyBacked.isEmpty) - } - - func testIndexes() { - var queue = OneOrManyQueue() - XCTAssertEqual(queue.startIndex, 0) - XCTAssertEqual(queue.endIndex, 0) - - // Non-empty. - queue.append(1) - XCTAssertEqual(queue.startIndex, 0) - XCTAssertEqual(queue.endIndex, 1) - } - - func testIndexesManyBacked() { - var queue = OneOrManyQueue.manyBacked - XCTAssertEqual(queue.startIndex, 0) - XCTAssertEqual(queue.endIndex, 0) - - for i in 1 ... 100 { - queue.append(i) - XCTAssertEqual(queue.startIndex, 0) - XCTAssertEqual(queue.endIndex, i) - } - } - - func testIndexAfter() { - var queue = OneOrManyQueue() - XCTAssertEqual(queue.startIndex, queue.endIndex) - XCTAssertEqual(queue.index(after: queue.startIndex), queue.endIndex) - - queue.append(1) - XCTAssertNotEqual(queue.startIndex, queue.endIndex) - XCTAssertEqual(queue.index(after: queue.startIndex), queue.endIndex) - } - - func testSubscript() throws { - var queue = OneOrManyQueue() - queue.append(42) - let index = try XCTUnwrap(queue.firstIndex(of: 42)) - XCTAssertEqual(queue[index], 42) - } - - func testSubscriptManyBacked() throws { - var queue = OneOrManyQueue.manyBacked - for i in 0 ... 100 { - queue.append(i) - } - - for i in 0 ... 100 { - XCTAssertEqual(queue[i], i) - } - } -} - -extension OneOrManyQueue where Element == Int { - static var manyBacked: Self { - var queue = OneOrManyQueue() - // Append and pop to move to the 'many' backing. - queue.append(1) - queue.append(2) - XCTAssertEqual(queue.pop(), 1) - XCTAssertEqual(queue.pop(), 2) - return queue - } -} diff --git a/Tests/GRPCHTTP2CoreTests/Server/Compression/ZlibTests.swift b/Tests/GRPCHTTP2CoreTests/Server/Compression/ZlibTests.swift deleted file mode 100644 index bcee1f3e2..000000000 --- a/Tests/GRPCHTTP2CoreTests/Server/Compression/ZlibTests.swift +++ /dev/null @@ -1,145 +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 NIOCore -import XCTest - -@testable import GRPCHTTP2Core - -final class ZlibTests: XCTestCase { - private let text = """ - Here's to the crazy ones. The misfits. The rebels. The troublemakers. The round pegs in the - square holes. The ones who see things differently. They're not fond of rules. And they have - no respect for the status quo. You can quote them, disagree with them, glorify or vilify them. - About the only thing you can't do is ignore them. Because they change things. They push the - human race forward. And while some may see them as the crazy ones, we see genius. Because - the people who are crazy enough to think they can change the world, are the ones who do. - """ - - private func compress(_ input: [UInt8], method: Zlib.Method) throws -> ByteBuffer { - let compressor = Zlib.Compressor(method: method) - defer { compressor.end() } - - var buffer = ByteBuffer() - try compressor.compress(input, into: &buffer) - return buffer - } - - private func decompress( - _ input: ByteBuffer, - method: Zlib.Method, - limit: Int = .max - ) throws -> [UInt8] { - let decompressor = Zlib.Decompressor(method: method) - defer { decompressor.end() } - - var input = input - return try decompressor.decompress(&input, limit: limit) - } - - func testRoundTripUsingDeflate() throws { - let original = Array(self.text.utf8) - let compressed = try self.compress(original, method: .deflate) - let decompressed = try self.decompress(compressed, method: .deflate) - XCTAssertEqual(original, decompressed) - } - - func testRoundTripUsingGzip() throws { - let original = Array(self.text.utf8) - let compressed = try self.compress(original, method: .gzip) - let decompressed = try self.decompress(compressed, method: .gzip) - XCTAssertEqual(original, decompressed) - } - - func testRepeatedCompresses() throws { - let original = Array(self.text.utf8) - let compressor = Zlib.Compressor(method: .deflate) - defer { compressor.end() } - - var compressed = ByteBuffer() - let bytesWritten = try compressor.compress(original, into: &compressed) - XCTAssertEqual(compressed.readableBytes, bytesWritten) - - for _ in 0 ..< 10 { - var buffer = ByteBuffer() - try compressor.compress(original, into: &buffer) - XCTAssertEqual(compressed, buffer) - } - } - - func testRepeatedDecompresses() throws { - let original = Array(self.text.utf8) - let decompressor = Zlib.Decompressor(method: .deflate) - defer { decompressor.end() } - - let compressed = try self.compress(original, method: .deflate) - var input = compressed - let decompressed = try decompressor.decompress(&input, limit: .max) - - for _ in 0 ..< 10 { - var input = compressed - let buffer = try decompressor.decompress(&input, limit: .max) - XCTAssertEqual(buffer, decompressed) - } - } - - func testDecompressGrowsOutputBuffer() throws { - // This compresses down to 17 bytes with deflate. The decompressor sets the output buffer to - // be double the size of the input buffer and will grow it if necessary. This test exercises - // that path. - let original = [UInt8](repeating: 0, count: 1024) - let compressed = try self.compress(original, method: .deflate) - let decompressed = try self.decompress(compressed, method: .deflate) - XCTAssertEqual(decompressed, original) - } - - func testDecompressRespectsLimit() throws { - let compressed = try self.compress(Array(self.text.utf8), method: .deflate) - let limit = compressed.readableBytes - 1 - XCTAssertThrowsError( - ofType: RPCError.self, - try self.decompress(compressed, method: .deflate, limit: limit) - ) { error in - XCTAssertEqual(error.code, .resourceExhausted) - } - } - - func testCompressAppendsToBuffer() throws { - let compressor = Zlib.Compressor(method: .deflate) - defer { compressor.end() } - - var buffer = ByteBuffer() - try compressor.compress(Array(repeating: 0, count: 1024), into: &buffer) - - // Should be some readable bytes. - let byteCount1 = buffer.readableBytes - XCTAssertGreaterThan(byteCount1, 0) - - try compressor.compress(Array(repeating: 1, count: 1024), into: &buffer) - - // Should be some readable bytes. - let byteCount2 = buffer.readableBytes - XCTAssertGreaterThan(byteCount2, byteCount1) - - let slice1 = buffer.readSlice(length: byteCount1)! - let decompressed1 = try self.decompress(slice1, method: .deflate) - XCTAssertEqual(decompressed1, Array(repeating: 0, count: 1024)) - - let decompressed2 = try self.decompress(buffer, method: .deflate) - XCTAssertEqual(decompressed2, Array(repeating: 1, count: 1024)) - } -} diff --git a/Tests/GRPCHTTP2CoreTests/Server/Connection/ServerConnectionManagementHandler+StateMachineTests.swift b/Tests/GRPCHTTP2CoreTests/Server/Connection/ServerConnectionManagementHandler+StateMachineTests.swift deleted file mode 100644 index 77a067a5b..000000000 --- a/Tests/GRPCHTTP2CoreTests/Server/Connection/ServerConnectionManagementHandler+StateMachineTests.swift +++ /dev/null @@ -1,240 +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 NIOCore -import NIOHTTP2 -import XCTest - -@testable import GRPCHTTP2Core - -final class ServerConnectionManagementHandlerStateMachineTests: XCTestCase { - private func makeStateMachine( - allowKeepaliveWithoutCalls: Bool = false, - minPingReceiveIntervalWithoutCalls: TimeAmount = .minutes(5), - goAwayPingData: HTTP2PingData = HTTP2PingData(withInteger: 42) - ) -> ServerConnectionManagementHandler.StateMachine { - return .init( - allowKeepaliveWithoutCalls: allowKeepaliveWithoutCalls, - minPingReceiveIntervalWithoutCalls: minPingReceiveIntervalWithoutCalls, - goAwayPingData: goAwayPingData - ) - } - - func testCloseAllStreamsWhenActive() { - var state = self.makeStateMachine() - state.streamOpened(1) - XCTAssertEqual(state.streamClosed(1), .startIdleTimer) - } - - func testCloseSomeStreamsWhenActive() { - var state = self.makeStateMachine() - state.streamOpened(1) - state.streamOpened(2) - XCTAssertEqual(state.streamClosed(2), .none) - } - - func testOpenAndCloseStreamWhenClosed() { - var state = self.makeStateMachine() - state.markClosed() - state.streamOpened(1) - XCTAssertEqual(state.streamClosed(1), .none) - } - - func testGracefulShutdownWhenNoOpenStreams() { - let pingData = HTTP2PingData(withInteger: 42) - var state = self.makeStateMachine(goAwayPingData: pingData) - XCTAssertEqual(state.startGracefulShutdown(), .sendGoAwayAndPing(pingData)) - } - - func testGracefulShutdownWhenClosing() { - let pingData = HTTP2PingData(withInteger: 42) - var state = self.makeStateMachine(goAwayPingData: pingData) - XCTAssertEqual(state.startGracefulShutdown(), .sendGoAwayAndPing(pingData)) - XCTAssertEqual(state.startGracefulShutdown(), .none) - } - - func testGracefulShutdownWhenClosed() { - let pingData = HTTP2PingData(withInteger: 42) - var state = self.makeStateMachine(goAwayPingData: pingData) - state.markClosed() - XCTAssertEqual(state.startGracefulShutdown(), .none) - } - - func testReceiveAckForGoAwayPingWhenStreamsOpenedBeforeShutdownOnly() { - let pingData = HTTP2PingData(withInteger: 42) - var state = self.makeStateMachine(goAwayPingData: pingData) - state.streamOpened(1) - XCTAssertEqual(state.startGracefulShutdown(), .sendGoAwayAndPing(pingData)) - XCTAssertEqual( - state.receivedPingAck(data: pingData), - .sendGoAway(lastStreamID: 1, close: false) - ) - } - - func testReceiveAckForGoAwayPingWhenStreamsOpenedBeforeAck() { - let pingData = HTTP2PingData(withInteger: 42) - var state = self.makeStateMachine(goAwayPingData: pingData) - XCTAssertEqual(state.startGracefulShutdown(), .sendGoAwayAndPing(pingData)) - state.streamOpened(1) - XCTAssertEqual( - state.receivedPingAck(data: pingData), - .sendGoAway(lastStreamID: 1, close: false) - ) - } - - func testReceiveAckForGoAwayPingWhenNoOpenStreams() { - let pingData = HTTP2PingData(withInteger: 42) - var state = self.makeStateMachine(goAwayPingData: pingData) - XCTAssertEqual(state.startGracefulShutdown(), .sendGoAwayAndPing(pingData)) - XCTAssertEqual( - state.receivedPingAck(data: pingData), - .sendGoAway(lastStreamID: .rootStream, close: true) - ) - } - - func testReceiveAckNotForGoAwayPing() { - let pingData = HTTP2PingData(withInteger: 42) - var state = self.makeStateMachine(goAwayPingData: pingData) - XCTAssertEqual(state.startGracefulShutdown(), .sendGoAwayAndPing(pingData)) - - let otherPingData = HTTP2PingData(withInteger: 0) - XCTAssertEqual(state.receivedPingAck(data: otherPingData), .none) - } - - func testReceivePingAckWhenActive() { - var state = self.makeStateMachine() - XCTAssertEqual(state.receivedPingAck(data: HTTP2PingData()), .none) - } - - func testReceivePingAckWhenClosed() { - var state = self.makeStateMachine() - state.markClosed() - XCTAssertEqual(state.receivedPingAck(data: HTTP2PingData()), .none) - } - - func testGracefulShutdownFlow() { - var state = self.makeStateMachine() - // Open a few streams. - state.streamOpened(1) - state.streamOpened(2) - - switch state.startGracefulShutdown() { - case .sendGoAwayAndPing(let pingData): - // Open another stream and then receive the ping ack. - state.streamOpened(3) - XCTAssertEqual( - state.receivedPingAck(data: pingData), - .sendGoAway(lastStreamID: 3, close: false) - ) - case .none: - XCTFail("Expected '.sendGoAwayAndPing'") - } - - // Both GOAWAY frames have been sent. Start closing streams. - XCTAssertEqual(state.streamClosed(1), .none) - XCTAssertEqual(state.streamClosed(2), .none) - XCTAssertEqual(state.streamClosed(3), .close) - } - - func testGracefulShutdownWhenNoOpenStreamsBeforeSecondGoAway() { - var state = self.makeStateMachine() - // Open a stream. - state.streamOpened(1) - - switch state.startGracefulShutdown() { - case .sendGoAwayAndPing(let pingData): - // Close the stream. This shouldn't lead to a close. - XCTAssertEqual(state.streamClosed(1), .none) - // Only on receiving the ack do we send a GOAWAY and close. - XCTAssertEqual( - state.receivedPingAck(data: pingData), - .sendGoAway(lastStreamID: 1, close: true) - ) - case .none: - XCTFail("Expected '.sendGoAwayAndPing'") - } - } - - func testPingStrikeUsingMinReceiveInterval( - state: inout ServerConnectionManagementHandler.StateMachine, - interval: TimeAmount, - expectedID id: HTTP2StreamID - ) { - var time = NIODeadline.now() - let data = HTTP2PingData() - - // The first ping is never a strike. - XCTAssertEqual(state.receivedPing(atTime: time, data: data), .sendAck) - - // Advance time by just less than the interval and get two strikes. - time = time + interval - .nanoseconds(1) - XCTAssertEqual(state.receivedPing(atTime: time, data: data), .sendAck) - XCTAssertEqual(state.receivedPing(atTime: time, data: data), .sendAck) - - // Advance time so that we're at one interval since the last valid ping. This isn't a - // strike (but doesn't reset strikes) and updates the last valid ping time. - time = time + .nanoseconds(1) - XCTAssertEqual(state.receivedPing(atTime: time, data: data), .sendAck) - - // Now get a third and final strike. - XCTAssertEqual(state.receivedPing(atTime: time, data: data), .enhanceYourCalmThenClose(id)) - } - - func testPingStrikesWhenKeepaliveIsNotPermittedWithoutCalls() { - let initialState = self.makeStateMachine( - allowKeepaliveWithoutCalls: false, - minPingReceiveIntervalWithoutCalls: .minutes(5) - ) - - var state = initialState - state.streamOpened(1) - self.testPingStrikeUsingMinReceiveInterval(state: &state, interval: .minutes(5), expectedID: 1) - - state = initialState - self.testPingStrikeUsingMinReceiveInterval(state: &state, interval: .hours(2), expectedID: 0) - } - - func testPingStrikesWhenKeepaliveIsPermittedWithoutCalls() { - var state = self.makeStateMachine( - allowKeepaliveWithoutCalls: true, - minPingReceiveIntervalWithoutCalls: .minutes(5) - ) - - self.testPingStrikeUsingMinReceiveInterval(state: &state, interval: .minutes(5), expectedID: 0) - } - - func testResetPingStrikeState() { - var state = self.makeStateMachine( - allowKeepaliveWithoutCalls: true, - minPingReceiveIntervalWithoutCalls: .minutes(5) - ) - - var time = NIODeadline.now() - let data = HTTP2PingData() - - // The first ping is never a strike. - XCTAssertEqual(state.receivedPing(atTime: time, data: data), .sendAck) - - // Advance time by less than the interval and get two strikes. - time = time + .minutes(1) - XCTAssertEqual(state.receivedPing(atTime: time, data: data), .sendAck) - XCTAssertEqual(state.receivedPing(atTime: time, data: data), .sendAck) - - // Reset the ping strike state and test ping strikes as normal. - state.resetKeepaliveState() - self.testPingStrikeUsingMinReceiveInterval(state: &state, interval: .minutes(5), expectedID: 0) - } -} diff --git a/Tests/GRPCHTTP2CoreTests/Server/Connection/ServerConnectionManagementHandlerTests.swift b/Tests/GRPCHTTP2CoreTests/Server/Connection/ServerConnectionManagementHandlerTests.swift deleted file mode 100644 index b4bfb0066..000000000 --- a/Tests/GRPCHTTP2CoreTests/Server/Connection/ServerConnectionManagementHandlerTests.swift +++ /dev/null @@ -1,410 +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 NIOCore -import NIOEmbedded -import NIOHTTP2 -import XCTest - -@testable import GRPCHTTP2Core - -final class ServerConnectionManagementHandlerTests: XCTestCase { - func testIdleTimeoutOnNewConnection() throws { - let connection = try Connection(maxIdleTime: .minutes(1)) - try connection.activate() - // Hit the max idle time. - connection.advanceTime(by: .minutes(1)) - - // Follow the graceful shutdown flow. - try self.testGracefulShutdown(connection: connection, lastStreamID: 0) - - // Closed because no streams were open. - try connection.waitUntilClosed() - } - - func testIdleTimerIsCancelledWhenStreamIsOpened() throws { - let connection = try Connection(maxIdleTime: .minutes(1)) - try connection.activate() - - // Open a stream to cancel the idle timer and run through the max idle time. - connection.streamOpened(1) - connection.advanceTime(by: .minutes(1)) - - // No GOAWAY frame means the timer was cancelled. - XCTAssertNil(try connection.readFrame()) - } - - func testIdleTimerStartsWhenAllStreamsAreClosed() throws { - let connection = try Connection(maxIdleTime: .minutes(1)) - try connection.activate() - - // Open a stream to cancel the idle timer and run through the max idle time. - connection.streamOpened(1) - connection.advanceTime(by: .minutes(1)) - XCTAssertNil(try connection.readFrame()) - - // Close the stream to start the timer again. - connection.streamClosed(1) - connection.advanceTime(by: .minutes(1)) - - // Follow the graceful shutdown flow. - try self.testGracefulShutdown(connection: connection, lastStreamID: 1) - - // Closed because no streams were open. - try connection.waitUntilClosed() - } - - func testMaxAge() throws { - let connection = try Connection(maxAge: .minutes(1)) - try connection.activate() - - // Open some streams. - connection.streamOpened(1) - connection.streamOpened(3) - - // Run to the max age and follow the graceful shutdown flow. - connection.advanceTime(by: .minutes(1)) - try self.testGracefulShutdown(connection: connection, lastStreamID: 3) - - // Close the streams. - connection.streamClosed(1) - connection.streamClosed(3) - - // Connection will be closed now. - try connection.waitUntilClosed() - } - - func testGracefulShutdownRatchetsDownStreamID() throws { - // This test uses the idle timeout to trigger graceful shutdown. The mechanism is the same - // regardless of how it's triggered. - let connection = try Connection(maxIdleTime: .minutes(1)) - try connection.activate() - - // Trigger the shutdown, but open a stream during shutdown. - connection.advanceTime(by: .minutes(1)) - try self.testGracefulShutdown( - connection: connection, - lastStreamID: 1, - streamToOpenBeforePingAck: 1 - ) - - // Close the stream to trigger closing the connection. - connection.streamClosed(1) - try connection.waitUntilClosed() - } - - func testGracefulShutdownGracePeriod() throws { - // This test uses the idle timeout to trigger graceful shutdown. The mechanism is the same - // regardless of how it's triggered. - let connection = try Connection( - maxIdleTime: .minutes(1), - maxGraceTime: .seconds(5) - ) - try connection.activate() - - // Trigger the shutdown, but open a stream during shutdown. - connection.advanceTime(by: .minutes(1)) - try self.testGracefulShutdown( - connection: connection, - lastStreamID: 1, - streamToOpenBeforePingAck: 1 - ) - - // Wait out the grace period without closing the stream. - connection.advanceTime(by: .seconds(5)) - try connection.waitUntilClosed() - } - - func testKeepaliveOnNewConnection() throws { - let connection = try Connection( - keepaliveTime: .minutes(5), - keepaliveTimeout: .seconds(5) - ) - try connection.activate() - - // Wait for the keep alive timer to fire which should cause the server to send a keep - // alive PING. - connection.advanceTime(by: .minutes(5)) - let frame1 = try XCTUnwrap(connection.readFrame()) - XCTAssertEqual(frame1.streamID, .rootStream) - try XCTAssertPing(frame1.payload) { data, ack in - XCTAssertFalse(ack) - // Data is opaque, send it back. - try connection.ping(data: data, ack: true) - } - - // Run past the timeout, nothing should happen. - connection.advanceTime(by: .seconds(5)) - XCTAssertNil(try connection.readFrame()) - } - - func testKeepaliveStartsAfterReadLoop() throws { - let connection = try Connection( - keepaliveTime: .minutes(5), - keepaliveTimeout: .seconds(5) - ) - try connection.activate() - - // Write a frame into the channel _without_ calling channel read complete. This will cancel - // the keep alive timer. - let settings = HTTP2Frame(streamID: .rootStream, payload: .settings(.settings([]))) - connection.channel.pipeline.fireChannelRead(NIOAny(settings)) - - // Run out the keep alive timer, it shouldn't fire. - connection.advanceTime(by: .minutes(5)) - XCTAssertNil(try connection.readFrame()) - - // Fire channel read complete to start the keep alive timer again. - connection.channel.pipeline.fireChannelReadComplete() - - // Now expire the keep alive timer again, we should read out a PING frame. - connection.advanceTime(by: .minutes(5)) - let frame1 = try XCTUnwrap(connection.readFrame()) - XCTAssertEqual(frame1.streamID, .rootStream) - XCTAssertPing(frame1.payload) { data, ack in - XCTAssertFalse(ack) - } - } - - func testKeepaliveOnNewConnectionWithoutResponse() throws { - let connection = try Connection( - keepaliveTime: .minutes(5), - keepaliveTimeout: .seconds(5) - ) - try connection.activate() - - // Wait for the keep alive timer to fire which should cause the server to send a keep - // alive PING. - connection.advanceTime(by: .minutes(5)) - let frame1 = try XCTUnwrap(connection.readFrame()) - XCTAssertEqual(frame1.streamID, .rootStream) - XCTAssertPing(frame1.payload) { data, ack in - XCTAssertFalse(ack) - } - - // We didn't ack the PING, the connection should shutdown after the timeout. - connection.advanceTime(by: .seconds(5)) - try self.testGracefulShutdown(connection: connection, lastStreamID: 0) - - // Connection is closed now. - try connection.waitUntilClosed() - } - - func testClientKeepalivePolicing() throws { - let connection = try Connection( - allowKeepaliveWithoutCalls: true, - minPingIntervalWithoutCalls: .minutes(1) - ) - try connection.activate() - - // The first ping is valid, the second and third are strikes. - for _ in 1 ... 3 { - try connection.ping(data: HTTP2PingData(), ack: false) - XCTAssertNil(try connection.readFrame()) - } - - // The fourth ping is the third strike and triggers a GOAWAY. - try connection.ping(data: HTTP2PingData(), ack: false) - let frame = try XCTUnwrap(connection.readFrame()) - XCTAssertEqual(frame.streamID, .rootStream) - XCTAssertGoAway(frame.payload) { streamID, error, data in - XCTAssertEqual(streamID, .rootStream) - XCTAssertEqual(error, .enhanceYourCalm) - XCTAssertEqual(data, ByteBuffer(string: "too_many_pings")) - } - - // The server should close the connection. - try connection.waitUntilClosed() - } - - func testClientKeepaliveWithPermissibleIntervals() throws { - let connection = try Connection( - allowKeepaliveWithoutCalls: true, - minPingIntervalWithoutCalls: .minutes(1), - manualClock: true - ) - try connection.activate() - - for _ in 1 ... 100 { - try connection.ping(data: HTTP2PingData(), ack: false) - XCTAssertNil(try connection.readFrame()) - - // Advance by the ping interval. - connection.advanceTime(by: .minutes(1)) - } - } - - func testClientKeepaliveResetState() throws { - let connection = try Connection( - allowKeepaliveWithoutCalls: true, - minPingIntervalWithoutCalls: .minutes(1) - ) - try connection.activate() - - func sendThreeKeepalivePings() throws { - // The first ping is valid, the second and third are strikes. - for _ in 1 ... 3 { - try connection.ping(data: HTTP2PingData(), ack: false) - XCTAssertNil(try connection.readFrame()) - } - } - - try sendThreeKeepalivePings() - - // "send" a HEADERS frame and flush to reset keep alive state. - connection.syncView.wroteHeadersFrame() - connection.syncView.connectionWillFlush() - - // As above, the first ping is valid, the next two are strikes. - try sendThreeKeepalivePings() - - // The next ping is the third strike and triggers a GOAWAY. - try connection.ping(data: HTTP2PingData(), ack: false) - let frame = try XCTUnwrap(connection.readFrame()) - XCTAssertEqual(frame.streamID, .rootStream) - XCTAssertGoAway(frame.payload) { streamID, error, data in - XCTAssertEqual(streamID, .rootStream) - XCTAssertEqual(error, .enhanceYourCalm) - XCTAssertEqual(data, ByteBuffer(string: "too_many_pings")) - } - - // The server should close the connection. - try connection.waitUntilClosed() - } -} - -extension ServerConnectionManagementHandlerTests { - private func testGracefulShutdown( - connection: Connection, - lastStreamID: HTTP2StreamID, - streamToOpenBeforePingAck: HTTP2StreamID? = nil - ) throws { - let frame1 = try XCTUnwrap(connection.readFrame()) - XCTAssertEqual(frame1.streamID, .rootStream) - XCTAssertGoAway(frame1.payload) { streamID, errorCode, _ in - XCTAssertEqual(streamID, .maxID) - XCTAssertEqual(errorCode, .noError) - } - - // Followed by a PING - let frame2 = try XCTUnwrap(connection.readFrame()) - XCTAssertEqual(frame2.streamID, .rootStream) - try XCTAssertPing(frame2.payload) { data, ack in - XCTAssertFalse(ack) - - if let id = streamToOpenBeforePingAck { - connection.streamOpened(id) - } - - // Send the PING ACK. - try connection.ping(data: data, ack: true) - } - - // PING ACK triggers another GOAWAY. - let frame3 = try XCTUnwrap(connection.readFrame()) - XCTAssertEqual(frame3.streamID, .rootStream) - XCTAssertGoAway(frame3.payload) { streamID, errorCode, _ in - XCTAssertEqual(streamID, lastStreamID) - XCTAssertEqual(errorCode, .noError) - } - } -} - -extension ServerConnectionManagementHandlerTests { - struct Connection { - let channel: EmbeddedChannel - let streamDelegate: any NIOHTTP2StreamDelegate - let syncView: ServerConnectionManagementHandler.SyncView - - var loop: EmbeddedEventLoop { - self.channel.embeddedEventLoop - } - - private let clock: ServerConnectionManagementHandler.Clock - - init( - maxIdleTime: TimeAmount? = nil, - maxAge: TimeAmount? = nil, - maxGraceTime: TimeAmount? = nil, - keepaliveTime: TimeAmount? = nil, - keepaliveTimeout: TimeAmount? = nil, - allowKeepaliveWithoutCalls: Bool = false, - minPingIntervalWithoutCalls: TimeAmount = .minutes(5), - manualClock: Bool = false - ) throws { - if manualClock { - self.clock = .manual(ServerConnectionManagementHandler.Clock.Manual()) - } else { - self.clock = .nio - } - - let loop = EmbeddedEventLoop() - let handler = ServerConnectionManagementHandler( - eventLoop: loop, - maxIdleTime: maxIdleTime, - maxAge: maxAge, - maxGraceTime: maxGraceTime, - keepaliveTime: keepaliveTime, - keepaliveTimeout: keepaliveTimeout, - allowKeepaliveWithoutCalls: allowKeepaliveWithoutCalls, - minPingIntervalWithoutCalls: minPingIntervalWithoutCalls, - requireALPN: false, - clock: self.clock - ) - - self.streamDelegate = handler.http2StreamDelegate - self.syncView = handler.syncView - self.channel = EmbeddedChannel(handler: handler, loop: loop) - } - - func activate() throws { - try self.channel.connect(to: SocketAddress(ipAddress: "127.0.0.1", port: 0)).wait() - } - - func advanceTime(by delta: TimeAmount) { - switch self.clock { - case .nio: - () - case .manual(let clock): - clock.advance(by: delta) - } - - self.loop.advanceTime(by: delta) - } - - func streamOpened(_ id: HTTP2StreamID) { - self.streamDelegate.streamCreated(id, channel: self.channel) - } - - func streamClosed(_ id: HTTP2StreamID) { - self.streamDelegate.streamClosed(id, channel: self.channel) - } - - func ping(data: HTTP2PingData, ack: Bool) throws { - let frame = HTTP2Frame(streamID: .rootStream, payload: .ping(data, ack: ack)) - try self.channel.writeInbound(frame) - } - - func readFrame() throws -> HTTP2Frame? { - return try self.channel.readOutbound(as: HTTP2Frame.self) - } - - func waitUntilClosed() throws { - self.channel.embeddedEventLoop.run() - try self.channel.closeFuture.wait() - } - } -} diff --git a/Tests/GRPCHTTP2CoreTests/Server/GRPCServerStreamHandlerTests.swift b/Tests/GRPCHTTP2CoreTests/Server/GRPCServerStreamHandlerTests.swift deleted file mode 100644 index 72486d92d..000000000 --- a/Tests/GRPCHTTP2CoreTests/Server/GRPCServerStreamHandlerTests.swift +++ /dev/null @@ -1,1075 +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 NIOCore -import NIOEmbedded -import NIOHPACK -import NIOHTTP2 -import XCTest - -@testable import GRPCHTTP2Core - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -final class GRPCServerStreamHandlerTests: XCTestCase { - func testH2FramesAreIgnored() throws { - let channel = EmbeddedChannel() - let handler = GRPCServerStreamHandler( - scheme: .http, - acceptedEncodings: [], - maxPayloadSize: 1, - methodDescriptorPromise: channel.eventLoop.makePromise(of: MethodDescriptor.self) - ) - try channel.pipeline.syncOperations.addHandler(handler) - - let framesToBeIgnored: [HTTP2Frame.FramePayload] = [ - .ping(.init(), ack: false), - .goAway(lastStreamID: .rootStream, errorCode: .cancel, opaqueData: nil), - // TODO: uncomment when it's possible to build a `StreamPriorityData`. - // .priority( - // HTTP2Frame.StreamPriorityData(exclusive: false, dependency: .rootStream, weight: 4) - // ), - .settings(.ack), - .pushPromise(.init(pushedStreamID: .maxID, headers: [:])), - .windowUpdate(windowSizeIncrement: 4), - .alternativeService(origin: nil, field: nil), - .origin([]), - ] - - for toBeIgnored in framesToBeIgnored { - XCTAssertNoThrow(try channel.writeInbound(toBeIgnored)) - XCTAssertNil(try channel.readInbound(as: HTTP2Frame.FramePayload.self)) - } - } - - func testClientInitialMetadataWithoutContentTypeResultsInRejectedRPC() throws { - let channel = EmbeddedChannel() - let handler = GRPCServerStreamHandler( - scheme: .http, - acceptedEncodings: [], - maxPayloadSize: 1, - methodDescriptorPromise: channel.eventLoop.makePromise(of: MethodDescriptor.self) - ) - try channel.pipeline.syncOperations.addHandler(handler) - - // Receive client's initial metadata without content-type - let clientInitialMetadata: HPACKHeaders = [ - GRPCHTTP2Keys.path.rawValue: "/test/test", - GRPCHTTP2Keys.scheme.rawValue: "http", - GRPCHTTP2Keys.method.rawValue: "POST", - GRPCHTTP2Keys.te.rawValue: "trailers", - ] - XCTAssertNoThrow( - try channel.writeInbound( - HTTP2Frame.FramePayload.headers(.init(headers: clientInitialMetadata)) - ) - ) - - // Make sure we have sent a trailers-only response - let writtenTrailersOnlyResponse = try channel.assertReadHeadersOutbound() - - XCTAssertEqual(writtenTrailersOnlyResponse.headers, [":status": "415"]) - XCTAssertTrue(writtenTrailersOnlyResponse.endStream) - } - - func testClientInitialMetadataWithoutMethodResultsInRejectedRPC() throws { - let channel = EmbeddedChannel() - let handler = GRPCServerStreamHandler( - scheme: .http, - acceptedEncodings: [], - maxPayloadSize: 1, - methodDescriptorPromise: channel.eventLoop.makePromise(of: MethodDescriptor.self) - ) - try channel.pipeline.syncOperations.addHandler(handler) - - // Receive client's initial metadata without :method - let clientInitialMetadata: HPACKHeaders = [ - GRPCHTTP2Keys.path.rawValue: "/test/test", - GRPCHTTP2Keys.scheme.rawValue: "http", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - GRPCHTTP2Keys.te.rawValue: "trailers", - ] - XCTAssertNoThrow( - try channel.writeInbound( - HTTP2Frame.FramePayload.headers(.init(headers: clientInitialMetadata)) - ) - ) - - // Make sure we have sent a trailers-only response - let writtenTrailersOnlyResponse = try channel.assertReadHeadersOutbound() - - XCTAssertEqual( - writtenTrailersOnlyResponse.headers, - [ - GRPCHTTP2Keys.status.rawValue: "200", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - GRPCHTTP2Keys.grpcStatus.rawValue: String(Status.Code.invalidArgument.rawValue), - GRPCHTTP2Keys.grpcStatusMessage.rawValue: - ":method header is expected to be present and have a value of \"POST\".", - ] - ) - XCTAssertTrue(writtenTrailersOnlyResponse.endStream) - } - - func testClientInitialMetadataWithoutSchemeResultsInRejectedRPC() throws { - let channel = EmbeddedChannel() - let handler = GRPCServerStreamHandler( - scheme: .http, - acceptedEncodings: [], - maxPayloadSize: 1, - methodDescriptorPromise: channel.eventLoop.makePromise(of: MethodDescriptor.self) - ) - try channel.pipeline.syncOperations.addHandler(handler) - - // Receive client's initial metadata without :scheme - let clientInitialMetadata: HPACKHeaders = [ - GRPCHTTP2Keys.path.rawValue: "/test/test", - GRPCHTTP2Keys.method.rawValue: "POST", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - GRPCHTTP2Keys.te.rawValue: "trailers", - ] - XCTAssertNoThrow( - try channel.writeInbound( - HTTP2Frame.FramePayload.headers(.init(headers: clientInitialMetadata)) - ) - ) - - // Make sure we have sent a trailers-only response - let writtenTrailersOnlyResponse = try channel.assertReadHeadersOutbound() - - XCTAssertEqual( - writtenTrailersOnlyResponse.headers, - [ - GRPCHTTP2Keys.status.rawValue: "200", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - GRPCHTTP2Keys.grpcStatus.rawValue: String(Status.Code.invalidArgument.rawValue), - GRPCHTTP2Keys.grpcStatusMessage.rawValue: - ":scheme header must be present and one of \"http\" or \"https\".", - ] - ) - XCTAssertTrue(writtenTrailersOnlyResponse.endStream) - } - - func testClientInitialMetadataWithoutPathResultsInRejectedRPC() throws { - let channel = EmbeddedChannel() - let handler = GRPCServerStreamHandler( - scheme: .http, - acceptedEncodings: [], - maxPayloadSize: 1, - methodDescriptorPromise: channel.eventLoop.makePromise(of: MethodDescriptor.self) - ) - try channel.pipeline.syncOperations.addHandler(handler) - - // Receive client's initial metadata without :path - let clientInitialMetadata: HPACKHeaders = [ - GRPCHTTP2Keys.scheme.rawValue: "http", - GRPCHTTP2Keys.method.rawValue: "POST", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - GRPCHTTP2Keys.te.rawValue: "trailers", - ] - XCTAssertNoThrow( - try channel.writeInbound( - HTTP2Frame.FramePayload.headers(.init(headers: clientInitialMetadata)) - ) - ) - - // Make sure we have sent a trailers-only response - let writtenTrailersOnlyResponse = try channel.assertReadHeadersOutbound() - - XCTAssertEqual( - writtenTrailersOnlyResponse.headers, - [ - GRPCHTTP2Keys.status.rawValue: "200", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - GRPCHTTP2Keys.grpcStatus.rawValue: String(Status.Code.invalidArgument.rawValue), - GRPCHTTP2Keys.grpcStatusMessage.rawValue: "No :path header has been set.", - ] - ) - XCTAssertTrue(writtenTrailersOnlyResponse.endStream) - } - - func testNotAcceptedEncodingResultsInRejectedRPC() throws { - let channel = EmbeddedChannel() - let handler = GRPCServerStreamHandler( - scheme: .http, - acceptedEncodings: [], - maxPayloadSize: 100, - methodDescriptorPromise: channel.eventLoop.makePromise(of: MethodDescriptor.self) - ) - try channel.pipeline.syncOperations.addHandler(handler) - - // Receive client's initial metadata - let clientInitialMetadata: HPACKHeaders = [ - GRPCHTTP2Keys.path.rawValue: "/test/test", - GRPCHTTP2Keys.scheme.rawValue: "http", - GRPCHTTP2Keys.method.rawValue: "POST", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - GRPCHTTP2Keys.te.rawValue: "trailers", - GRPCHTTP2Keys.encoding.rawValue: "deflate", - ] - XCTAssertNoThrow( - try channel.writeInbound( - HTTP2Frame.FramePayload.headers(.init(headers: clientInitialMetadata)) - ) - ) - - // Make sure we have sent a trailers-only response - let writtenTrailersOnlyResponse = try channel.assertReadHeadersOutbound() - - XCTAssertEqual( - writtenTrailersOnlyResponse.headers, - [ - GRPCHTTP2Keys.status.rawValue: "200", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - GRPCHTTP2Keys.grpcStatus.rawValue: String(Status.Code.unimplemented.rawValue), - GRPCHTTP2Keys.grpcStatusMessage.rawValue: - "deflate compression is not supported; supported algorithms are listed in grpc-accept-encoding", - GRPCHTTP2Keys.acceptEncoding.rawValue: "identity", - ] - ) - XCTAssertTrue(writtenTrailersOnlyResponse.endStream) - } - - func testOverMaximumPayloadSize() throws { - let channel = EmbeddedChannel() - let handler = GRPCServerStreamHandler( - scheme: .http, - acceptedEncodings: [], - maxPayloadSize: 1, - methodDescriptorPromise: channel.eventLoop.makePromise(of: MethodDescriptor.self) - ) - try channel.pipeline.syncOperations.addHandler(handler) - - // Receive client's initial metadata - let clientInitialMetadata: HPACKHeaders = [ - GRPCHTTP2Keys.path.rawValue: "/test/test", - GRPCHTTP2Keys.scheme.rawValue: "http", - GRPCHTTP2Keys.method.rawValue: "POST", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - GRPCHTTP2Keys.te.rawValue: "trailers", - ] - XCTAssertNoThrow( - try channel.writeInbound( - HTTP2Frame.FramePayload.headers(.init(headers: clientInitialMetadata)) - ) - ) - - // Make sure we haven't sent back an error response, and that we read the initial metadata - XCTAssertNil(try channel.readOutbound(as: HTTP2Frame.FramePayload.self)) - XCTAssertEqual( - try channel.readInbound(as: RPCRequestPart.self), - RPCRequestPart.metadata(Metadata(headers: clientInitialMetadata)) - ) - - // Write back server's initial metadata - let headers: HPACKHeaders = [ - "some-custom-header": "some-custom-value" - ] - let serverInitialMetadata = RPCResponsePart.metadata(Metadata(headers: headers)) - XCTAssertNoThrow(try channel.writeOutbound(serverInitialMetadata)) - - // Make sure we wrote back the initial metadata - let writtenHeaders = try channel.assertReadHeadersOutbound() - - XCTAssertEqual( - writtenHeaders.headers, - [ - GRPCHTTP2Keys.status.rawValue: "200", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - "some-custom-header": "some-custom-value", - ] - ) - - // Receive client's message - var buffer = ByteBuffer() - buffer.writeInteger(UInt8(0)) // not compressed - buffer.writeInteger(UInt32(42)) // message length - buffer.writeRepeatingByte(0, count: 42) // message - let clientDataPayload = HTTP2Frame.FramePayload.Data(data: .byteBuffer(buffer), endStream: true) - XCTAssertThrowsError( - ofType: RPCError.self, - try channel.writeInbound(HTTP2Frame.FramePayload.data(clientDataPayload)) - ) { error in - XCTAssertEqual(error.code, .internalError) - XCTAssertEqual(error.message, "Failed to decode message") - } - - // Make sure we haven't sent a response back and that we didn't read the received message - XCTAssertNil(try channel.readOutbound(as: HTTP2Frame.FramePayload.self)) - XCTAssertNil(try channel.readInbound(as: RPCRequestPart.self)) - } - - func testClientEndsStream() throws { - let channel = EmbeddedChannel() - let handler = GRPCServerStreamHandler( - scheme: .http, - acceptedEncodings: [], - maxPayloadSize: 1, - methodDescriptorPromise: channel.eventLoop.makePromise(of: MethodDescriptor.self), - skipStateMachineAssertions: true - ) - try channel.pipeline.syncOperations.addHandler(handler) - - // Receive client's initial metadata with end stream set - let clientInitialMetadata: HPACKHeaders = [ - GRPCHTTP2Keys.path.rawValue: "/test/test", - GRPCHTTP2Keys.scheme.rawValue: "http", - GRPCHTTP2Keys.method.rawValue: "POST", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - GRPCHTTP2Keys.te.rawValue: "trailers", - ] - XCTAssertNoThrow( - try channel.writeInbound( - HTTP2Frame.FramePayload.headers(.init(headers: clientInitialMetadata, endStream: true)) - ) - ) - - // Make sure we haven't sent back an error response, and that we read the initial metadata - XCTAssertNil(try channel.readOutbound(as: HTTP2Frame.FramePayload.self)) - XCTAssertEqual( - try channel.readInbound(as: RPCRequestPart.self), - RPCRequestPart.metadata(Metadata(headers: clientInitialMetadata)) - ) - - // Write back server's initial metadata - let headers: HPACKHeaders = [ - "some-custom-header": "some-custom-value" - ] - let serverInitialMetadata = RPCResponsePart.metadata(Metadata(headers: headers)) - XCTAssertNoThrow(try channel.writeOutbound(serverInitialMetadata)) - - // Make sure we wrote back the initial metadata - let writtenHeaders = try channel.assertReadHeadersOutbound() - - XCTAssertEqual( - writtenHeaders.headers, - [ - GRPCHTTP2Keys.status.rawValue: "200", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - "some-custom-header": "some-custom-value", - ] - ) - - // We should throw if the client sends another message, since it's closed the stream already. - var buffer = ByteBuffer() - buffer.writeInteger(UInt8(0)) // not compressed - buffer.writeInteger(UInt32(42)) // message length - buffer.writeRepeatingByte(0, count: 42) // message - let clientDataPayload = HTTP2Frame.FramePayload.Data(data: .byteBuffer(buffer), endStream: true) - XCTAssertThrowsError( - ofType: RPCError.self, - try channel.writeInbound(HTTP2Frame.FramePayload.data(clientDataPayload)) - ) { error in - XCTAssertEqual(error.code, .internalError) - XCTAssertEqual(error.message, "Invalid state") - } - } - - func testNormalFlow() throws { - let channel = EmbeddedChannel() - let handler = GRPCServerStreamHandler( - scheme: .http, - acceptedEncodings: [], - maxPayloadSize: 42, - methodDescriptorPromise: channel.eventLoop.makePromise(of: MethodDescriptor.self), - skipStateMachineAssertions: true - ) - try channel.pipeline.syncOperations.addHandler(handler) - - // Receive client's initial metadata - let clientInitialMetadata: HPACKHeaders = [ - GRPCHTTP2Keys.path.rawValue: "/test/test", - GRPCHTTP2Keys.scheme.rawValue: "http", - GRPCHTTP2Keys.method.rawValue: "POST", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - GRPCHTTP2Keys.te.rawValue: "trailers", - ] - XCTAssertNoThrow( - try channel.writeInbound( - HTTP2Frame.FramePayload.headers(.init(headers: clientInitialMetadata)) - ) - ) - - // Make sure we haven't sent back an error response, and that we read the initial metadata - XCTAssertNil(try channel.readOutbound(as: HTTP2Frame.FramePayload.self)) - XCTAssertEqual( - try channel.readInbound(as: RPCRequestPart.self), - RPCRequestPart.metadata(Metadata(headers: clientInitialMetadata)) - ) - - // Write back server's initial metadata - let headers: HPACKHeaders = [ - "some-custom-header": "some-custom-value" - ] - let serverInitialMetadata = RPCResponsePart.metadata(Metadata(headers: headers)) - XCTAssertNoThrow(try channel.writeOutbound(serverInitialMetadata)) - - // Make sure we wrote back the initial metadata - let writtenHeaders = try channel.assertReadHeadersOutbound() - - XCTAssertEqual( - writtenHeaders.headers, - [ - GRPCHTTP2Keys.status.rawValue: "200", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - "some-custom-header": "some-custom-value", - ] - ) - - // Receive client's message - var buffer = ByteBuffer() - buffer.writeInteger(UInt8(0)) // not compressed - buffer.writeInteger(UInt32(42)) // message length - buffer.writeRepeatingByte(0, count: 42) // message - let clientDataPayload = HTTP2Frame.FramePayload.Data(data: .byteBuffer(buffer), endStream: true) - XCTAssertNoThrow(try channel.writeInbound(HTTP2Frame.FramePayload.data(clientDataPayload))) - - // Make sure we haven't sent back an error response, and that we read the message properly - XCTAssertNil(try channel.readOutbound(as: HTTP2Frame.FramePayload.self)) - XCTAssertEqual( - try channel.readInbound(as: RPCRequestPart.self), - RPCRequestPart.message([UInt8](repeating: 0, count: 42)) - ) - - // Write back response - let serverDataPayload = RPCResponsePart.message([UInt8](repeating: 1, count: 42)) - XCTAssertNoThrow(try channel.writeOutbound(serverDataPayload)) - - // Make sure we wrote back the right message - let writtenMessage = try channel.assertReadDataOutbound() - - var expectedBuffer = ByteBuffer() - expectedBuffer.writeInteger(UInt8(0)) // not compressed - expectedBuffer.writeInteger(UInt32(42)) // message length - expectedBuffer.writeRepeatingByte(1, count: 42) // message - XCTAssertEqual(writtenMessage.data, .byteBuffer(expectedBuffer)) - - // Send back status to end RPC - let trailers = RPCResponsePart.status( - .init(code: .dataLoss, message: "Test data loss"), - ["custom-header": "custom-value"] - ) - XCTAssertNoThrow(try channel.writeOutbound(trailers)) - - // Make sure we wrote back the status and trailers - let writtenStatus = try channel.assertReadHeadersOutbound() - - XCTAssertTrue(writtenStatus.endStream) - XCTAssertEqual( - writtenStatus.headers, - [ - GRPCHTTP2Keys.grpcStatus.rawValue: String(Status.Code.dataLoss.rawValue), - GRPCHTTP2Keys.grpcStatusMessage.rawValue: "Test data loss", - "custom-header": "custom-value", - ] - ) - - // Try writing and assert it throws to make sure we don't allow writes - // after closing. - XCTAssertThrowsError( - ofType: RPCError.self, - try channel.writeOutbound(trailers) - ) { error in - XCTAssertEqual(error.code, .internalError) - XCTAssertEqual(error.message, "Invalid state") - } - } - - func testReceiveMessageSplitAcrossMultipleBuffers() throws { - let channel = EmbeddedChannel() - let handler = GRPCServerStreamHandler( - scheme: .http, - acceptedEncodings: [], - maxPayloadSize: 100, - methodDescriptorPromise: channel.eventLoop.makePromise(of: MethodDescriptor.self) - ) - try channel.pipeline.syncOperations.addHandler(handler) - - // Receive client's initial metadata - let clientInitialMetadata: HPACKHeaders = [ - GRPCHTTP2Keys.path.rawValue: "/test/test", - GRPCHTTP2Keys.scheme.rawValue: "http", - GRPCHTTP2Keys.method.rawValue: "POST", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - GRPCHTTP2Keys.te.rawValue: "trailers", - ] - XCTAssertNoThrow( - try channel.writeInbound( - HTTP2Frame.FramePayload.headers(.init(headers: clientInitialMetadata)) - ) - ) - - // Make sure we haven't sent back an error response, and that we read the initial metadata - XCTAssertNil(try channel.readOutbound(as: HTTP2Frame.FramePayload.self)) - XCTAssertEqual( - try channel.readInbound(as: RPCRequestPart.self), - RPCRequestPart.metadata(Metadata(headers: clientInitialMetadata)) - ) - - // Write back server's initial metadata - let headers: HPACKHeaders = [ - "some-custom-header": "some-custom-value" - ] - let serverInitialMetadata = RPCResponsePart.metadata(Metadata(headers: headers)) - XCTAssertNoThrow(try channel.writeOutbound(serverInitialMetadata)) - - // Make sure we wrote back the initial metadata - let writtenHeaders = try channel.assertReadHeadersOutbound() - - XCTAssertEqual( - writtenHeaders.headers, - [ - GRPCHTTP2Keys.status.rawValue: "200", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - "some-custom-header": "some-custom-value", - ] - ) - - // Receive client's first message - var buffer = ByteBuffer() - buffer.writeInteger(UInt8(0)) // not compressed - XCTAssertNoThrow( - try channel.writeInbound(HTTP2Frame.FramePayload.data(.init(data: .byteBuffer(buffer)))) - ) - XCTAssertNil(try channel.readInbound(as: RPCRequestPart.self)) - - buffer.clear() - buffer.writeInteger(UInt32(30)) // message length - XCTAssertNoThrow( - try channel.writeInbound(HTTP2Frame.FramePayload.data(.init(data: .byteBuffer(buffer)))) - ) - XCTAssertNil(try channel.readInbound(as: RPCRequestPart.self)) - - buffer.clear() - buffer.writeRepeatingByte(0, count: 10) // first part of the message - XCTAssertNoThrow( - try channel.writeInbound(HTTP2Frame.FramePayload.data(.init(data: .byteBuffer(buffer)))) - ) - XCTAssertNil(try channel.readInbound(as: RPCRequestPart.self)) - - buffer.clear() - buffer.writeRepeatingByte(1, count: 10) // second part of the message - XCTAssertNoThrow( - try channel.writeInbound(HTTP2Frame.FramePayload.data(.init(data: .byteBuffer(buffer)))) - ) - XCTAssertNil(try channel.readInbound(as: RPCRequestPart.self)) - - buffer.clear() - buffer.writeRepeatingByte(2, count: 10) // third part of the message - XCTAssertNoThrow( - try channel.writeInbound(HTTP2Frame.FramePayload.data(.init(data: .byteBuffer(buffer)))) - ) - - // Make sure we haven't sent back an error response, and that we read the message properly - XCTAssertNil(try channel.readOutbound(as: HTTP2Frame.FramePayload.self)) - XCTAssertEqual( - try channel.readInbound(as: RPCRequestPart.self), - RPCRequestPart.message( - [UInt8](repeating: 0, count: 10) + [UInt8](repeating: 1, count: 10) - + [UInt8](repeating: 2, count: 10) - ) - ) - } - - func testReceiveMultipleHeaders() throws { - let channel = EmbeddedChannel() - let handler = GRPCServerStreamHandler( - scheme: .http, - acceptedEncodings: [], - maxPayloadSize: 100, - methodDescriptorPromise: channel.eventLoop.makePromise(of: MethodDescriptor.self) - ) - try channel.pipeline.syncOperations.addHandler(handler) - // Receive client's initial metadata - let clientInitialMetadata: HPACKHeaders = [ - GRPCHTTP2Keys.path.rawValue: "/test/test", - GRPCHTTP2Keys.scheme.rawValue: "http", - GRPCHTTP2Keys.method.rawValue: "POST", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - GRPCHTTP2Keys.te.rawValue: "trailers", - ] - try channel.writeInbound(HTTP2Frame.FramePayload.headers(.init(headers: clientInitialMetadata))) - XCTAssertNil(try channel.readOutbound(as: HTTP2Frame.FramePayload.self)) - - // Receive them again. Should be a protocol violation. - XCTAssertThrowsError( - ofType: RPCError.self, - try channel.writeInbound( - HTTP2Frame.FramePayload.headers(.init(headers: clientInitialMetadata)) - ) - ) { error in - XCTAssertEqual(error.code, .unavailable) - XCTAssertEqual(error.message, "Stream unexpectedly closed.") - } - let payload = try XCTUnwrap(channel.readOutbound(as: HTTP2Frame.FramePayload.self)) - - switch payload { - case .rstStream(let errorCode): - XCTAssertEqual(errorCode, .protocolError) - default: - XCTFail("Expected RST_STREAM, got \(payload)") - } - } - - func testSendMultipleMessagesInSingleBuffer() throws { - let channel = EmbeddedChannel() - let handler = GRPCServerStreamHandler( - scheme: .http, - acceptedEncodings: [], - maxPayloadSize: 100, - methodDescriptorPromise: channel.eventLoop.makePromise(of: MethodDescriptor.self) - ) - try channel.pipeline.syncOperations.addHandler(handler) - - // Receive client's initial metadata - let clientInitialMetadata: HPACKHeaders = [ - GRPCHTTP2Keys.path.rawValue: "/test/test", - GRPCHTTP2Keys.scheme.rawValue: "http", - GRPCHTTP2Keys.method.rawValue: "POST", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - GRPCHTTP2Keys.te.rawValue: "trailers", - ] - XCTAssertNoThrow( - try channel.writeInbound( - HTTP2Frame.FramePayload.headers(.init(headers: clientInitialMetadata)) - ) - ) - - // Make sure we haven't sent back an error response, and that we read the initial metadata - XCTAssertNil(try channel.readOutbound(as: HTTP2Frame.FramePayload.self)) - XCTAssertEqual( - try channel.readInbound(as: RPCRequestPart.self), - RPCRequestPart.metadata(Metadata(headers: clientInitialMetadata)) - ) - - // Write back server's initial metadata - let headers: HPACKHeaders = [ - "some-custom-header": "some-custom-value" - ] - let serverInitialMetadata = RPCResponsePart.metadata(Metadata(headers: headers)) - XCTAssertNoThrow(try channel.writeOutbound(serverInitialMetadata)) - - // Read out the metadata - _ = try channel.readOutbound(as: HTTP2Frame.FramePayload.self) - - // This is where this test actually begins. We want to write two messages - // without flushing, and make sure that no messages are sent down the pipeline - // until we flush. Once we flush, both messages should be sent in the same ByteBuffer. - - // Write back first message and make sure nothing's written in the channel. - XCTAssertNoThrow(channel.write(RPCResponsePart.message([UInt8](repeating: 1, count: 4)))) - XCTAssertNil(try channel.readOutbound(as: HTTP2Frame.FramePayload.self)) - - // Write back second message and make sure nothing's written in the channel. - XCTAssertNoThrow(channel.write(RPCResponsePart.message([UInt8](repeating: 2, count: 4)))) - XCTAssertNil(try channel.readOutbound(as: HTTP2Frame.FramePayload.self)) - - // Now flush and check we *do* write the data. - channel.flush() - - let writtenMessage = try channel.assertReadDataOutbound() - - // Make sure both messages have been framed together in the ByteBuffer. - XCTAssertEqual( - writtenMessage.data, - .byteBuffer( - .init(bytes: [ - // First message - 0, // Compression disabled - 0, 0, 0, 4, // Message length - 1, 1, 1, 1, // First message data - - // Second message - 0, // Compression disabled - 0, 0, 0, 4, // Message length - 2, 2, 2, 2, // Second message data - ]) - ) - ) - XCTAssertNil(try channel.readOutbound(as: HTTP2Frame.FramePayload.self)) - } - - func testMessageAndStatusAreNotReordered() throws { - let channel = EmbeddedChannel() - let handler = GRPCServerStreamHandler( - scheme: .http, - acceptedEncodings: [], - maxPayloadSize: 100, - methodDescriptorPromise: channel.eventLoop.makePromise(of: MethodDescriptor.self) - ) - try channel.pipeline.syncOperations.addHandler(handler) - - // Receive client's initial metadata - let clientInitialMetadata: HPACKHeaders = [ - GRPCHTTP2Keys.path.rawValue: "/test/test", - GRPCHTTP2Keys.scheme.rawValue: "http", - GRPCHTTP2Keys.method.rawValue: "POST", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - GRPCHTTP2Keys.te.rawValue: "trailers", - ] - XCTAssertNoThrow( - try channel.writeInbound( - HTTP2Frame.FramePayload.headers(.init(headers: clientInitialMetadata)) - ) - ) - - // Make sure we haven't sent back an error response, and that we read the initial metadata - XCTAssertNil(try channel.readOutbound(as: HTTP2Frame.FramePayload.self)) - XCTAssertEqual( - try channel.readInbound(as: RPCRequestPart.self), - RPCRequestPart.metadata(Metadata(headers: clientInitialMetadata)) - ) - - // Write back server's initial metadata - let serverInitialMetadata = RPCResponsePart.metadata(Metadata(headers: [:])) - XCTAssertNoThrow(try channel.writeOutbound(serverInitialMetadata)) - - // Read out the metadata - _ = try channel.readOutbound(as: HTTP2Frame.FramePayload.self) - - // This is where this test actually begins. We want to write a message followed - // by status and trailers, and only flush after both writes. - // Because messages are buffered and potentially bundled together in a single - // ByteBuffer by the GPRCMessageFramer, we want to make sure that the status - // and trailers won't be written before the messages. - - // Write back message and make sure nothing's written in the channel. - XCTAssertNoThrow(channel.write(RPCResponsePart.message([UInt8](repeating: 1, count: 4)))) - XCTAssertNil(try channel.readOutbound(as: HTTP2Frame.FramePayload.self)) - - // Write status + metadata and make sure nothing's written. - XCTAssertNoThrow(channel.write(RPCResponsePart.status(.init(code: .ok, message: ""), [:]))) - XCTAssertNil(try channel.readOutbound(as: HTTP2Frame.FramePayload.self)) - - // Now flush and check we *do* write the data in the right order: message first, - // trailers second. - channel.flush() - - let writtenMessage = try channel.assertReadDataOutbound() - - // Make sure we first get message. - XCTAssertEqual( - writtenMessage.data, - .byteBuffer( - .init(bytes: [ - // First message - 0, // Compression disabled - 0, 0, 0, 4, // Message length - 1, 1, 1, 1, // First message data - ]) - ) - ) - XCTAssertFalse(writtenMessage.endStream) - - // Make sure we get trailers. - let writtenTrailers = try channel.assertReadHeadersOutbound() - XCTAssertEqual(writtenTrailers.headers, ["grpc-status": "0"]) - XCTAssertTrue(writtenTrailers.endStream) - - // Make sure we get nothing else. - XCTAssertNil(try channel.readOutbound(as: HTTP2Frame.FramePayload.self)) - } - - func testMethodDescriptorPromiseSucceeds() throws { - let channel = EmbeddedChannel() - let promise = channel.eventLoop.makePromise(of: MethodDescriptor.self) - let handler = GRPCServerStreamHandler( - scheme: .http, - acceptedEncodings: [], - maxPayloadSize: 100, - methodDescriptorPromise: promise, - skipStateMachineAssertions: true - ) - try channel.pipeline.syncOperations.addHandler(handler) - - // Receive client's initial metadata - let clientInitialMetadata: HPACKHeaders = [ - GRPCHTTP2Keys.path.rawValue: "/SomeService/SomeMethod", - GRPCHTTP2Keys.scheme.rawValue: "http", - GRPCHTTP2Keys.method.rawValue: "POST", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - GRPCHTTP2Keys.te.rawValue: "trailers", - ] - XCTAssertNoThrow( - try channel.writeInbound( - HTTP2Frame.FramePayload.headers(.init(headers: clientInitialMetadata)) - ) - ) - - // Make sure we haven't sent back an error response, and that we read the initial metadata - XCTAssertNil(try channel.readOutbound(as: HTTP2Frame.FramePayload.self)) - XCTAssertEqual( - try channel.readInbound(as: RPCRequestPart.self), - RPCRequestPart.metadata(Metadata(headers: clientInitialMetadata)) - ) - - XCTAssertEqual( - try promise.futureResult.wait(), - MethodDescriptor(service: "SomeService", method: "SomeMethod") - ) - } - - func testMethodDescriptorPromiseIsFailedWhenHandlerRemoved() throws { - let channel = EmbeddedChannel() - let promise = channel.eventLoop.makePromise(of: MethodDescriptor.self) - let handler = GRPCServerStreamHandler( - scheme: .http, - acceptedEncodings: [], - maxPayloadSize: 100, - methodDescriptorPromise: promise, - skipStateMachineAssertions: true - ) - try channel.pipeline.syncOperations.addHandler(handler) - - try channel.pipeline.syncOperations.removeHandler(handler).wait() - - XCTAssertThrowsError( - ofType: RPCError.self, - try promise.futureResult.wait() - ) { error in - XCTAssertEqual(error.code, .unavailable) - XCTAssertEqual(error.message, "RPC stream was closed before we got any Metadata.") - } - } - - func testMethodDescriptorPromiseIsFailedIfRPCRejected() throws { - let channel = EmbeddedChannel() - let promise = channel.eventLoop.makePromise(of: MethodDescriptor.self) - let handler = GRPCServerStreamHandler( - scheme: .http, - acceptedEncodings: [], - maxPayloadSize: 100, - methodDescriptorPromise: promise, - skipStateMachineAssertions: true - ) - try channel.pipeline.syncOperations.addHandler(handler) - - // Receive client's initial metadata - let clientInitialMetadata: HPACKHeaders = [ - GRPCHTTP2Keys.path.rawValue: "SomeService/SomeMethod", - GRPCHTTP2Keys.scheme.rawValue: "http", - GRPCHTTP2Keys.method.rawValue: "POST", - GRPCHTTP2Keys.contentType.rawValue: "application/not-valid-contenttype", - GRPCHTTP2Keys.te.rawValue: "trailers", - ] - XCTAssertNoThrow( - try channel.writeInbound( - HTTP2Frame.FramePayload.headers(.init(headers: clientInitialMetadata)) - ) - ) - - XCTAssertThrowsError( - ofType: RPCError.self, - try promise.futureResult.wait() - ) { error in - XCTAssertEqual(error.code, .unavailable) - XCTAssertEqual(error.message, "RPC was rejected.") - } - } - - func testUnexpectedStreamClose_ErrorFired() throws { - let channel = EmbeddedChannel() - let promise = channel.eventLoop.makePromise(of: MethodDescriptor.self) - let handler = GRPCServerStreamHandler( - scheme: .http, - acceptedEncodings: [], - maxPayloadSize: 100, - methodDescriptorPromise: promise, - skipStateMachineAssertions: true - ) - try channel.pipeline.syncOperations.addHandler(handler) - - // Receive client's initial metadata - let clientInitialMetadata: HPACKHeaders = [ - GRPCHTTP2Keys.path.rawValue: "/SomeService/SomeMethod", - GRPCHTTP2Keys.scheme.rawValue: "http", - GRPCHTTP2Keys.method.rawValue: "POST", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - GRPCHTTP2Keys.te.rawValue: "trailers", - ] - XCTAssertNoThrow( - try channel.writeInbound( - HTTP2Frame.FramePayload.headers(.init(headers: clientInitialMetadata)) - ) - ) - - // Make sure we haven't sent back an error response, and that we read the initial metadata - XCTAssertNil(try channel.readOutbound(as: HTTP2Frame.FramePayload.self)) - XCTAssertEqual( - try channel.readInbound(as: RPCRequestPart.self), - RPCRequestPart.metadata(Metadata(headers: clientInitialMetadata)) - ) - - // An error is fired down the pipeline - let thrownError = ChannelError.connectTimeout(.milliseconds(100)) - channel.pipeline.fireErrorCaught(thrownError) - - // The server handler simply forwards the error. - XCTAssertThrowsError( - ofType: type(of: thrownError), - try channel.throwIfErrorCaught() - ) { error in - XCTAssertEqual(error, thrownError) - } - - // We should now be closed: check we can't write anymore. - XCTAssertThrowsError( - ofType: RPCError.self, - try channel.writeOutbound(RPCResponsePart.metadata(Metadata())) - ) { error in - XCTAssertEqual(error.code, .internalError) - XCTAssertEqual(error.message, "Invalid state") - } - } - - func testUnexpectedStreamClose_ChannelInactive() throws { - let channel = EmbeddedChannel() - let promise = channel.eventLoop.makePromise(of: MethodDescriptor.self) - let handler = GRPCServerStreamHandler( - scheme: .http, - acceptedEncodings: [], - maxPayloadSize: 100, - methodDescriptorPromise: promise, - skipStateMachineAssertions: true - ) - try channel.pipeline.syncOperations.addHandler(handler) - - // Receive client's initial metadata - let clientInitialMetadata: HPACKHeaders = [ - GRPCHTTP2Keys.path.rawValue: "/SomeService/SomeMethod", - GRPCHTTP2Keys.scheme.rawValue: "http", - GRPCHTTP2Keys.method.rawValue: "POST", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - GRPCHTTP2Keys.te.rawValue: "trailers", - ] - XCTAssertNoThrow( - try channel.writeInbound( - HTTP2Frame.FramePayload.headers(.init(headers: clientInitialMetadata)) - ) - ) - - // Make sure we haven't sent back an error response, and that we read the initial metadata - XCTAssertNil(try channel.readOutbound(as: HTTP2Frame.FramePayload.self)) - XCTAssertEqual( - try channel.readInbound(as: RPCRequestPart.self), - RPCRequestPart.metadata(Metadata(headers: clientInitialMetadata)) - ) - - // Channel becomes inactive - channel.pipeline.fireChannelInactive() - - // The server handler fires an error - XCTAssertThrowsError( - ofType: RPCError.self, - try channel.throwIfErrorCaught() - ) { error in - XCTAssertEqual(error.code, .unavailable) - XCTAssertEqual(error.message, "Stream unexpectedly closed.") - } - - // We should now be closed: check we can't write anymore. - XCTAssertThrowsError( - ofType: RPCError.self, - try channel.writeOutbound(RPCResponsePart.metadata(Metadata())) - ) { error in - XCTAssertEqual(error.code, .internalError) - XCTAssertEqual(error.message, "Invalid state") - } - } - - func testUnexpectedStreamClose_ResetStreamFrame() throws { - let channel = EmbeddedChannel() - let promise = channel.eventLoop.makePromise(of: MethodDescriptor.self) - let handler = GRPCServerStreamHandler( - scheme: .http, - acceptedEncodings: [], - maxPayloadSize: 100, - methodDescriptorPromise: promise, - skipStateMachineAssertions: true - ) - try channel.pipeline.syncOperations.addHandler(handler) - - // Receive client's initial metadata - let clientInitialMetadata: HPACKHeaders = [ - GRPCHTTP2Keys.path.rawValue: "/SomeService/SomeMethod", - GRPCHTTP2Keys.scheme.rawValue: "http", - GRPCHTTP2Keys.method.rawValue: "POST", - GRPCHTTP2Keys.contentType.rawValue: "application/grpc", - GRPCHTTP2Keys.te.rawValue: "trailers", - ] - XCTAssertNoThrow( - try channel.writeInbound( - HTTP2Frame.FramePayload.headers(.init(headers: clientInitialMetadata)) - ) - ) - - // Make sure we haven't sent back an error response, and that we read the initial metadata - XCTAssertNil(try channel.readOutbound(as: HTTP2Frame.FramePayload.self)) - XCTAssertEqual( - try channel.readInbound(as: RPCRequestPart.self), - RPCRequestPart.metadata(Metadata(headers: clientInitialMetadata)) - ) - - // We receive RST_STREAM frame - // Assert the server handler fires an error - XCTAssertThrowsError( - ofType: RPCError.self, - try channel.writeInbound( - HTTP2Frame.FramePayload.rstStream(.internalError) - ) - ) { error in - XCTAssertEqual(error.code, .unavailable) - XCTAssertEqual(error.message, "Stream unexpectedly closed: a RST_STREAM frame was received.") - } - - // We should now be closed: check we can't write anymore. - XCTAssertThrowsError( - ofType: RPCError.self, - try channel.writeOutbound(RPCResponsePart.metadata(Metadata())) - ) { error in - XCTAssertEqual(error.code, .internalError) - XCTAssertEqual(error.message, "Invalid state") - } - } -} - -extension EmbeddedChannel { - fileprivate func assertReadHeadersOutbound() throws -> HTTP2Frame.FramePayload.Headers { - guard - case .headers(let writtenHeaders) = try XCTUnwrap( - try self.readOutbound(as: HTTP2Frame.FramePayload.self) - ) - else { - throw TestError.assertionFailure("Expected to write headers") - } - return writtenHeaders - } - - fileprivate func assertReadDataOutbound() throws -> HTTP2Frame.FramePayload.Data { - guard - case .data(let writtenMessage) = try XCTUnwrap( - try self.readOutbound(as: HTTP2Frame.FramePayload.self) - ) - else { - throw TestError.assertionFailure("Expected to write data") - } - return writtenMessage - } -} - -private enum TestError: Error { - case assertionFailure(String) -} diff --git a/Tests/GRPCHTTP2CoreTests/Server/HTTP2ServerTransportConfigTests.swift b/Tests/GRPCHTTP2CoreTests/Server/HTTP2ServerTransportConfigTests.swift deleted file mode 100644 index d7e1c06b8..000000000 --- a/Tests/GRPCHTTP2CoreTests/Server/HTTP2ServerTransportConfigTests.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 GRPCHTTP2Core -import XCTest - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -final class HTTP2ServerTransportConfigTests: XCTestCase { - func testCompressionDefaults() { - let config = HTTP2ServerTransport.Config.Compression.defaults - XCTAssertEqual(config.enabledAlgorithms, .none) - } - - func testKeepaliveDefaults() { - let config = HTTP2ServerTransport.Config.Keepalive.defaults - XCTAssertEqual(config.time, .seconds(7200)) - XCTAssertEqual(config.timeout, .seconds(20)) - XCTAssertEqual(config.clientBehavior.allowWithoutCalls, false) - XCTAssertEqual(config.clientBehavior.minPingIntervalWithoutCalls, .seconds(300)) - } - - func testConnectionDefaults() { - let config = HTTP2ServerTransport.Config.Connection.defaults - XCTAssertNil(config.maxAge) - XCTAssertNil(config.maxGraceTime) - XCTAssertNil(config.maxIdleTime) - } - - func testHTTP2Defaults() { - let config = HTTP2ServerTransport.Config.HTTP2.defaults - XCTAssertEqual(config.maxFrameSize, 16_384) - XCTAssertEqual(config.targetWindowSize, 65_535) - XCTAssertNil(config.maxConcurrentStreams) - } - - func testRPCDefaults() { - let config = HTTP2ServerTransport.Config.RPC.defaults - XCTAssertEqual(config.maxRequestPayloadSize, 4_194_304) - } -} diff --git a/Tests/GRPCHTTP2CoreTests/Test Utilities/AtomicCounter.swift b/Tests/GRPCHTTP2CoreTests/Test Utilities/AtomicCounter.swift deleted file mode 100644 index b9e9fb5b8..000000000 --- a/Tests/GRPCHTTP2CoreTests/Test Utilities/AtomicCounter.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 Synchronization - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -final class AtomicCounter: Sendable { - private let counter: Atomic - - init(_ initialValue: Int = 0) { - self.counter = Atomic(initialValue) - } - - var value: Int { - self.counter.load(ordering: .sequentiallyConsistent) - } - - @discardableResult - func increment() -> (oldValue: Int, newValue: Int) { - self.counter.add(1, ordering: .sequentiallyConsistent) - } -} diff --git a/Tests/GRPCHTTP2CoreTests/Test Utilities/MethodDescriptor+Common.swift b/Tests/GRPCHTTP2CoreTests/Test Utilities/MethodDescriptor+Common.swift deleted file mode 100644 index e7c1ef50a..000000000 --- a/Tests/GRPCHTTP2CoreTests/Test Utilities/MethodDescriptor+Common.swift +++ /dev/null @@ -1,34 +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 - -extension MethodDescriptor { - static var echoGet: Self { - MethodDescriptor(service: "echo.Echo", method: "Get") - } - - static var echoUpdate: Self { - MethodDescriptor(service: "echo.Echo", method: "Update") - } -} - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -extension MethodConfig.Name { - init(_ descriptor: MethodDescriptor) { - self = MethodConfig.Name(service: descriptor.service, method: descriptor.method) - } -} diff --git a/Tests/GRPCHTTP2CoreTests/Test Utilities/Task+Poll.swift b/Tests/GRPCHTTP2CoreTests/Test Utilities/Task+Poll.swift deleted file mode 100644 index 1183b9df1..000000000 --- a/Tests/GRPCHTTP2CoreTests/Test Utilities/Task+Poll.swift +++ /dev/null @@ -1,37 +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, *) -extension Task where Success == Never, Failure == Never { - static func poll( - every interval: Duration, - timeLimit: Duration = .seconds(5), - until predicate: () async throws -> Bool - ) async throws -> Bool { - var start = ContinuousClock.now - let end = start.advanced(by: timeLimit) - - while end > .now { - let canReturn = try await predicate() - if canReturn { return true } - - start = start.advanced(by: interval) - try await Task.sleep(until: start) - } - - return false - } -} diff --git a/Tests/GRPCHTTP2CoreTests/Test Utilities/XCTest+Utilities.swift b/Tests/GRPCHTTP2CoreTests/Test Utilities/XCTest+Utilities.swift deleted file mode 100644 index 2e036f985..000000000 --- a/Tests/GRPCHTTP2CoreTests/Test Utilities/XCTest+Utilities.swift +++ /dev/null @@ -1,81 +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 XCTest - -func XCTAssertThrowsError( - ofType: E.Type, - file: StaticString = #filePath, - line: UInt = #line, - _ expression: @autoclosure () throws -> T, - _ errorHandler: (E) -> Void -) { - XCTAssertThrowsError(try expression(), file: file, line: line) { error in - guard let error = error as? E else { - return XCTFail("Error had unexpected type '\(type(of: error))'", file: file, line: line) - } - errorHandler(error) - } -} - -func XCTAssertDescription( - _ subject: some CustomStringConvertible, - _ expected: String, - file: StaticString = #filePath, - line: UInt = #line -) { - XCTAssertEqual(String(describing: subject), expected, file: file, line: line) -} - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -func XCTUnwrapAsync(_ expression: () async throws -> T?) async throws -> T { - let value = try await expression() - return try XCTUnwrap(value) -} - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -func XCTAssertThrowsErrorAsync( - ofType: E.Type = E.self, - _ expression: () async throws -> T, - errorHandler: (E) -> Void -) async { - do { - _ = try await expression() - XCTFail("Expression didn't throw") - } catch let error as E { - errorHandler(error) - } catch { - XCTFail("Error had unexpected type '\(type(of: error))'") - } -} - -func XCTAssert(_ value: Any, as type: T.Type, _ verify: (T) throws -> Void) rethrows { - if let value = value as? T { - try verify(value) - } else { - XCTFail("\(value) couldn't be cast to \(T.self)") - } -} - -@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) -func XCTPoll( - every interval: Duration, - timeLimit: Duration = .seconds(5), - until predicate: () async throws -> Bool -) async throws { - let becameTrue = try await Task.poll(every: interval, timeLimit: timeLimit, until: predicate) - XCTAssertTrue(becameTrue, "Predicate didn't return true within \(timeLimit)") -} diff --git a/Tests/GRPCHTTP2CoreTests/XCTest+FramePayload.swift b/Tests/GRPCHTTP2CoreTests/XCTest+FramePayload.swift deleted file mode 100644 index b6892d0db..000000000 --- a/Tests/GRPCHTTP2CoreTests/XCTest+FramePayload.swift +++ /dev/null @@ -1,43 +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 NIOCore -import NIOHTTP2 -import XCTest - -func XCTAssertGoAway( - _ payload: HTTP2Frame.FramePayload, - verify: (HTTP2StreamID, HTTP2ErrorCode, ByteBuffer?) throws -> Void = { _, _, _ in } -) rethrows { - switch payload { - case .goAway(let lastStreamID, let errorCode, let opaqueData): - try verify(lastStreamID, errorCode, opaqueData) - default: - XCTFail("Expected '.goAway' got '\(payload)'") - } -} - -func XCTAssertPing( - _ payload: HTTP2Frame.FramePayload, - verify: (HTTP2PingData, Bool) throws -> Void = { _, _ in } -) rethrows { - switch payload { - case .ping(let data, ack: let ack): - try verify(data, ack) - default: - XCTFail("Expected '.ping' got '\(payload)'") - } -} diff --git a/Tests/GRPCHTTP2TransportTests/ControlService.swift b/Tests/GRPCHTTP2TransportTests/ControlService.swift deleted file mode 100644 index 9139d9a42..000000000 --- a/Tests/GRPCHTTP2TransportTests/ControlService.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. - */ - -import GRPCCore - -import struct Foundation.Data - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -struct ControlService: ControlStreamingServiceProtocol { - func unary( - request: ServerRequest.Stream, - context: ServerContext - ) async throws -> ServerResponse.Stream { - try await self.handle(request: request) - } - - func serverStream( - request: ServerRequest.Stream, - context: ServerContext - ) async throws -> ServerResponse.Stream { - try await self.handle(request: request) - } - - func clientStream( - request: ServerRequest.Stream, - context: ServerContext - ) async throws -> ServerResponse.Stream { - try await self.handle(request: request) - } - - func bidiStream( - request: ServerRequest.Stream, - context: ServerContext - ) async throws -> ServerResponse.Stream { - try await self.handle(request: request) - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension ControlService { - private func handle( - request: ServerRequest.Stream - ) async throws -> ServerResponse.Stream { - var iterator = request.messages.makeAsyncIterator() - - guard let message = try await iterator.next() else { - // Empty input stream, empty output stream. - return ServerResponse.Stream { _ in [:] } - } - - // Check if the request is for a trailers-only response. - if message.hasStatus, message.isTrailersOnly { - let trailers = message.echoMetadataInTrailers ? request.metadata.echo() : [:] - let code = Status.Code(rawValue: message.status.code.rawValue).flatMap { RPCError.Code($0) } - - if let code = code { - throw RPCError(code: code, message: message.status.message, metadata: trailers) - } else { - // Invalid code, the request is invalid, so throw an appropriate error. - throw RPCError( - code: .invalidArgument, - message: "Trailers only response must use a non-OK status code" - ) - } - } - - // Not a trailers-only response. Should the metadata be echo'd back? - let metadata = message.echoMetadataInHeaders ? request.metadata.echo() : [:] - - // The iterator needs to be transferred into the response. This is okay: we won't touch the - // iterator again from the current concurrency domain. - let transfer = UnsafeTransfer(iterator) - - return ServerResponse.Stream(metadata: metadata) { writer in - // Finish dealing with the first message. - switch try await self.processMessage(message, metadata: request.metadata, writer: writer) { - case .return(let metadata): - return metadata - case .continue: - () - } - - var iterator = transfer.wrappedValue - // Process the rest of the messages. - while let message = try await iterator.next() { - switch try await self.processMessage(message, metadata: request.metadata, writer: writer) { - case .return(let metadata): - return metadata - case .continue: - () - } - } - - // Input stream finished without explicitly setting a status; finish the RPC cleanly. - return [:] - } - } - - private enum NextProcessingStep { - case `return`(Metadata) - case `continue` - } - - private func processMessage( - _ input: ControlInput, - metadata: Metadata, - writer: RPCWriter - ) async throws -> NextProcessingStep { - // If messages were requested, build a response and send them back. - if input.numberOfMessages > 0 { - let output = ControlOutput.with { - $0.payload = Data( - repeating: UInt8(truncatingIfNeeded: input.messageParams.content), - count: Int(input.messageParams.size) - ) - } - - for _ in 0 ..< input.numberOfMessages { - try await writer.write(output) - } - } - - // Check whether the RPC should be finished (i.e. the input `hasStatus`). - guard input.hasStatus else { - if input.echoMetadataInTrailers { - // There was no status in the input, but echo metadata in trailers was set. This is an - // implicit 'ok' status. - let trailers = input.echoMetadataInTrailers ? metadata.echo() : [:] - return .return(trailers) - } else { - // No status, and not echoing back metadata. Continue consuming the input stream. - return .continue - } - } - - // Build the trailers. - let trailers = input.echoMetadataInTrailers ? metadata.echo() : [:] - - if input.status.code == .ok { - return .return(trailers) - } - - // Non-OK status code, throw an error. - let code = Status.Code(rawValue: input.status.code.rawValue).flatMap { RPCError.Code($0) } - - if let code = code { - // Valid error code, throw it. - throw RPCError(code: code, message: input.status.message, metadata: trailers) - } else { - // Invalid error code, throw an appropriate error. - throw RPCError( - code: .invalidArgument, - message: "Invalid error code '\(input.status.code)'" - ) - } - } -} - -extension Metadata { - fileprivate func echo() -> Self { - var copy = Metadata() - copy.reserveCapacity(self.count) - - for (key, value) in self { - // Header field names mustn't contain ":". - let key = "echo-" + key.replacingOccurrences(of: ":", with: "") - switch value { - case .string(let stringValue): - copy.addString(stringValue, forKey: key) - case .binary(let binaryValue): - copy.addBinary(binaryValue, forKey: key) - } - } - - return copy - } -} - -private struct UnsafeTransfer { - var wrappedValue: Wrapped - - init(_ wrappedValue: Wrapped) { - self.wrappedValue = wrappedValue - } -} - -extension UnsafeTransfer: @unchecked Sendable {} diff --git a/Tests/GRPCHTTP2TransportTests/Generated/control.grpc.swift b/Tests/GRPCHTTP2TransportTests/Generated/control.grpc.swift deleted file mode 100644 index 8833dc856..000000000 --- a/Tests/GRPCHTTP2TransportTests/Generated/control.grpc.swift +++ /dev/null @@ -1,490 +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. - -// DO NOT EDIT. -// swift-format-ignore-file -// -// Generated by the gRPC Swift generator plugin for the protocol buffer compiler. -// Source: control.proto -// -// For information on using the generated types, please see the documentation: -// https://github.com/grpc/grpc-swift - -import GRPCCore -import GRPCProtobuf - -internal enum Control { - internal static let descriptor = GRPCCore.ServiceDescriptor.Control - internal enum Method { - internal enum Unary { - internal typealias Input = ControlInput - internal typealias Output = ControlOutput - internal static let descriptor = GRPCCore.MethodDescriptor( - service: Control.descriptor.fullyQualifiedService, - method: "Unary" - ) - } - internal enum ServerStream { - internal typealias Input = ControlInput - internal typealias Output = ControlOutput - internal static let descriptor = GRPCCore.MethodDescriptor( - service: Control.descriptor.fullyQualifiedService, - method: "ServerStream" - ) - } - internal enum ClientStream { - internal typealias Input = ControlInput - internal typealias Output = ControlOutput - internal static let descriptor = GRPCCore.MethodDescriptor( - service: Control.descriptor.fullyQualifiedService, - method: "ClientStream" - ) - } - internal enum BidiStream { - internal typealias Input = ControlInput - internal typealias Output = ControlOutput - internal static let descriptor = GRPCCore.MethodDescriptor( - service: Control.descriptor.fullyQualifiedService, - method: "BidiStream" - ) - } - internal static let descriptors: [GRPCCore.MethodDescriptor] = [ - Unary.descriptor, - ServerStream.descriptor, - ClientStream.descriptor, - BidiStream.descriptor - ] - } - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - internal typealias StreamingServiceProtocol = ControlStreamingServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - internal typealias ServiceProtocol = ControlServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - internal typealias ClientProtocol = ControlClientProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - internal typealias Client = ControlClient -} - -extension GRPCCore.ServiceDescriptor { - internal static let Control = Self( - package: "", - service: "Control" - ) -} - -/// A controllable service for testing. -/// -/// The control service has one RPC of each kind, the input to each RPC controls -/// the output. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -internal protocol ControlStreamingServiceProtocol: GRPCCore.RegistrableRPCService { - func unary( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream - - func serverStream( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream - - func clientStream( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream - - func bidiStream( - 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 Control.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: Control.Method.Unary.descriptor, - deserializer: GRPCProtobuf.ProtobufDeserializer(), - serializer: GRPCProtobuf.ProtobufSerializer(), - handler: { request, context in - try await self.unary( - request: request, - context: context - ) - } - ) - router.registerHandler( - forMethod: Control.Method.ServerStream.descriptor, - deserializer: GRPCProtobuf.ProtobufDeserializer(), - serializer: GRPCProtobuf.ProtobufSerializer(), - handler: { request, context in - try await self.serverStream( - request: request, - context: context - ) - } - ) - router.registerHandler( - forMethod: Control.Method.ClientStream.descriptor, - deserializer: GRPCProtobuf.ProtobufDeserializer(), - serializer: GRPCProtobuf.ProtobufSerializer(), - handler: { request, context in - try await self.clientStream( - request: request, - context: context - ) - } - ) - router.registerHandler( - forMethod: Control.Method.BidiStream.descriptor, - deserializer: GRPCProtobuf.ProtobufDeserializer(), - serializer: GRPCProtobuf.ProtobufSerializer(), - handler: { request, context in - try await self.bidiStream( - request: request, - context: context - ) - } - ) - } -} - -/// A controllable service for testing. -/// -/// The control service has one RPC of each kind, the input to each RPC controls -/// the output. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -internal protocol ControlServiceProtocol: Control.StreamingServiceProtocol { - func unary( - request: GRPCCore.ServerRequest.Single, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Single - - func serverStream( - request: GRPCCore.ServerRequest.Single, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream - - func clientStream( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Single - - func bidiStream( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream -} - -/// Partial conformance to `ControlStreamingServiceProtocol`. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension Control.ServiceProtocol { - internal func unary( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream { - let response = try await self.unary( - request: GRPCCore.ServerRequest.Single(stream: request), - context: context - ) - return GRPCCore.ServerResponse.Stream(single: response) - } - - internal func serverStream( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream { - let response = try await self.serverStream( - request: GRPCCore.ServerRequest.Single(stream: request), - context: context - ) - return response - } - - internal func clientStream( - request: GRPCCore.ServerRequest.Stream, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Stream { - let response = try await self.clientStream( - request: request, - context: context - ) - return GRPCCore.ServerResponse.Stream(single: response) - } -} - -/// A controllable service for testing. -/// -/// The control service has one RPC of each kind, the input to each RPC controls -/// the output. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -internal protocol ControlClientProtocol: Sendable { - func unary( - 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 serverStream( - 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 - - func clientStream( - 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 - - func bidiStream( - 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 Control.ClientProtocol { - internal func unary( - 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.unary( - request: request, - serializer: GRPCProtobuf.ProtobufSerializer(), - deserializer: GRPCProtobuf.ProtobufDeserializer(), - options: options, - body - ) - } - - internal func serverStream( - 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.serverStream( - request: request, - serializer: GRPCProtobuf.ProtobufSerializer(), - deserializer: GRPCProtobuf.ProtobufDeserializer(), - options: options, - body - ) - } - - internal func clientStream( - 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.clientStream( - request: request, - serializer: GRPCProtobuf.ProtobufSerializer(), - deserializer: GRPCProtobuf.ProtobufDeserializer(), - options: options, - body - ) - } - - internal func bidiStream( - 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.bidiStream( - 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 Control.ClientProtocol { - internal func unary( - _ message: ControlInput, - 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.unary( - request: request, - options: options, - handleResponse - ) - } - - internal func serverStream( - _ message: ControlInput, - 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.serverStream( - request: request, - options: options, - handleResponse - ) - } - - internal func clientStream( - 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.clientStream( - request: request, - options: options, - handleResponse - ) - } - - internal func bidiStream( - 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.bidiStream( - request: request, - options: options, - handleResponse - ) - } -} - -/// A controllable service for testing. -/// -/// The control service has one RPC of each kind, the input to each RPC controls -/// the output. -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -internal struct ControlClient: Control.ClientProtocol { - private let client: GRPCCore.GRPCClient - - internal init(wrapping client: GRPCCore.GRPCClient) { - self.client = client - } - - internal func unary( - 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: Control.Method.Unary.descriptor, - serializer: serializer, - deserializer: deserializer, - options: options, - handler: body - ) - } - - internal func serverStream( - 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: Control.Method.ServerStream.descriptor, - serializer: serializer, - deserializer: deserializer, - options: options, - handler: body - ) - } - - internal func clientStream( - 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: Control.Method.ClientStream.descriptor, - serializer: serializer, - deserializer: deserializer, - options: options, - handler: body - ) - } - - internal func bidiStream( - 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: Control.Method.BidiStream.descriptor, - serializer: serializer, - deserializer: deserializer, - options: options, - handler: body - ) - } -} \ No newline at end of file diff --git a/Tests/GRPCHTTP2TransportTests/Generated/control.pb.swift b/Tests/GRPCHTTP2TransportTests/Generated/control.pb.swift deleted file mode 100644 index 435bd944c..000000000 --- a/Tests/GRPCHTTP2TransportTests/Generated/control.pb.swift +++ /dev/null @@ -1,446 +0,0 @@ -// DO NOT EDIT. -// swift-format-ignore-file -// swiftlint:disable all -// -// Generated by the Swift generator plugin for the protocol buffer compiler. -// Source: control.proto -// -// For information on using the generated types, please see the documentation: -// https://github.com/apple/swift-protobuf/ - -// -// 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 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 StatusCode: SwiftProtobuf.Enum, Swift.CaseIterable { - typealias RawValue = Int - 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 `internal` // = 13 - case unavailable // = 14 - case dataLoss // = 15 - case unauthenticated // = 16 - case UNRECOGNIZED(Int) - - init() { - self = .ok - } - - init?(rawValue: Int) { - switch rawValue { - case 0: self = .ok - case 1: self = .cancelled - case 2: self = .unknown - case 3: self = .invalidArgument - case 4: self = .deadlineExceeded - case 5: self = .notFound - case 6: self = .alreadyExists - case 7: self = .permissionDenied - case 8: self = .resourceExhausted - case 9: self = .failedPrecondition - case 10: self = .aborted - case 11: self = .outOfRange - case 12: self = .unimplemented - case 13: self = .internal - case 14: self = .unavailable - case 15: self = .dataLoss - case 16: self = .unauthenticated - default: self = .UNRECOGNIZED(rawValue) - } - } - - var rawValue: Int { - switch self { - case .ok: return 0 - case .cancelled: return 1 - case .unknown: return 2 - case .invalidArgument: return 3 - case .deadlineExceeded: return 4 - case .notFound: return 5 - case .alreadyExists: return 6 - case .permissionDenied: return 7 - case .resourceExhausted: return 8 - case .failedPrecondition: return 9 - case .aborted: return 10 - case .outOfRange: return 11 - case .unimplemented: return 12 - case .internal: return 13 - case .unavailable: return 14 - case .dataLoss: return 15 - case .unauthenticated: return 16 - case .UNRECOGNIZED(let i): return i - } - } - - // The compiler won't synthesize support with the UNRECOGNIZED case. - static let allCases: [StatusCode] = [ - .ok, - .cancelled, - .unknown, - .invalidArgument, - .deadlineExceeded, - .notFound, - .alreadyExists, - .permissionDenied, - .resourceExhausted, - .failedPrecondition, - .aborted, - .outOfRange, - .unimplemented, - .internal, - .unavailable, - .dataLoss, - .unauthenticated, - ] - -} - -struct ControlInput: 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. - - /// Whether metadata should be echo'd back in the initial metadata. - /// - /// Ignored if the initial metadata has already been sent back to the - /// client. - /// - /// Each header field name in the request headers will be prefixed with - /// "echo-". For example the header field name "foo" will be returned - /// as "echo-foo. Note that semicolons aren't valid in HTTP header field - /// names (apart from pseudo headers). As such all semicolons should be - /// removed (":path" should become "echo-path"). - var echoMetadataInHeaders: Bool = false - - /// Parameters for response messages. - var messageParams: PayloadParameters { - get {return _messageParams ?? PayloadParameters()} - set {_messageParams = newValue} - } - /// Returns true if `messageParams` has been explicitly set. - var hasMessageParams: Bool {return self._messageParams != nil} - /// Clears the value of `messageParams`. Subsequent reads from it will return its default value. - mutating func clearMessageParams() {self._messageParams = nil} - - /// The number of response messages. - var numberOfMessages: Int32 = 0 - - /// The status code and message to use at the end of the RPC. - /// - /// If this is set then the RPC will be ended after `number_of_messages` - /// messages have been sent back to the client. - var status: RPCStatus { - get {return _status ?? RPCStatus()} - set {_status = newValue} - } - /// Returns true if `status` has been explicitly set. - var hasStatus: Bool {return self._status != nil} - /// Clears the value of `status`. Subsequent reads from it will return its default value. - mutating func clearStatus() {self._status = nil} - - /// Whether the response should be trailers only. - /// - /// Ignored unless it's set on the first message on the stream. When set - /// the RPC will be completed with a trailers-only response using the - /// status code and message from 'status'. The request metadata will be - /// included if 'echo_metadata_in_trailers' is set. - /// - /// If this is set then 'number_of_messages', 'message_params', and - /// 'echo_metadata_in_headers' are ignored. - var isTrailersOnly: Bool = false - - /// Whether metadata should be echo'd back in the trailing metadata. - /// - /// Ignored unless 'status' is set. - /// - /// Each header field name in the request headers will be prefixed with - /// "echo-". For example the header field name "foo" will be returned - /// as "echo-foo. Note that semicolons aren't valid in HTTP header field - /// names (apart from pseudo headers). As such all semicolons should be - /// removed (":path" should become "echo-path"). - var echoMetadataInTrailers: Bool = false - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} - - fileprivate var _messageParams: PayloadParameters? = nil - fileprivate var _status: RPCStatus? = nil -} - -struct RPCStatus: 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. - - /// Status code indicating the outcome of the RPC. - var code: StatusCode = .ok - - /// The message to include with the 'code' at the end of the RPC. - var message: String = String() - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -struct PayloadParameters: 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 bytes to put into the output payload. - var size: Int32 = 0 - - /// The content to use in the payload. The value is truncated to an octet. - var content: UInt32 = 0 - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -struct ControlOutput: @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. - - var payload: Data = Data() - - var unknownFields = SwiftProtobuf.UnknownStorage() - - init() {} -} - -// MARK: - Code below here is support for the SwiftProtobuf runtime. - -extension StatusCode: SwiftProtobuf._ProtoNameProviding { - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 0: .same(proto: "OK"), - 1: .same(proto: "CANCELLED"), - 2: .same(proto: "UNKNOWN"), - 3: .same(proto: "INVALID_ARGUMENT"), - 4: .same(proto: "DEADLINE_EXCEEDED"), - 5: .same(proto: "NOT_FOUND"), - 6: .same(proto: "ALREADY_EXISTS"), - 7: .same(proto: "PERMISSION_DENIED"), - 8: .same(proto: "RESOURCE_EXHAUSTED"), - 9: .same(proto: "FAILED_PRECONDITION"), - 10: .same(proto: "ABORTED"), - 11: .same(proto: "OUT_OF_RANGE"), - 12: .same(proto: "UNIMPLEMENTED"), - 13: .same(proto: "INTERNAL"), - 14: .same(proto: "UNAVAILABLE"), - 15: .same(proto: "DATA_LOSS"), - 16: .same(proto: "UNAUTHENTICATED"), - ] -} - -extension ControlInput: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = "ControlInput" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .standard(proto: "echo_metadata_in_headers"), - 2: .standard(proto: "message_params"), - 3: .standard(proto: "number_of_messages"), - 5: .same(proto: "status"), - 6: .standard(proto: "is_trailers_only"), - 4: .standard(proto: "echo_metadata_in_trailers"), - ] - - 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.echoMetadataInHeaders) }() - case 2: try { try decoder.decodeSingularMessageField(value: &self._messageParams) }() - case 3: try { try decoder.decodeSingularInt32Field(value: &self.numberOfMessages) }() - case 4: try { try decoder.decodeSingularBoolField(value: &self.echoMetadataInTrailers) }() - case 5: try { try decoder.decodeSingularMessageField(value: &self._status) }() - case 6: try { try decoder.decodeSingularBoolField(value: &self.isTrailersOnly) }() - 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.echoMetadataInHeaders != false { - try visitor.visitSingularBoolField(value: self.echoMetadataInHeaders, fieldNumber: 1) - } - try { if let v = self._messageParams { - try visitor.visitSingularMessageField(value: v, fieldNumber: 2) - } }() - if self.numberOfMessages != 0 { - try visitor.visitSingularInt32Field(value: self.numberOfMessages, fieldNumber: 3) - } - if self.echoMetadataInTrailers != false { - try visitor.visitSingularBoolField(value: self.echoMetadataInTrailers, fieldNumber: 4) - } - try { if let v = self._status { - try visitor.visitSingularMessageField(value: v, fieldNumber: 5) - } }() - if self.isTrailersOnly != false { - try visitor.visitSingularBoolField(value: self.isTrailersOnly, fieldNumber: 6) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: ControlInput, rhs: ControlInput) -> Bool { - if lhs.echoMetadataInHeaders != rhs.echoMetadataInHeaders {return false} - if lhs._messageParams != rhs._messageParams {return false} - if lhs.numberOfMessages != rhs.numberOfMessages {return false} - if lhs._status != rhs._status {return false} - if lhs.isTrailersOnly != rhs.isTrailersOnly {return false} - if lhs.echoMetadataInTrailers != rhs.echoMetadataInTrailers {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension RPCStatus: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = "RPCStatus" - 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.decodeSingularEnumField(value: &self.code) }() - case 2: try { try decoder.decodeSingularStringField(value: &self.message) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if self.code != .ok { - try visitor.visitSingularEnumField(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: RPCStatus, rhs: RPCStatus) -> 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 PayloadParameters: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = "PayloadParameters" - static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ - 1: .same(proto: "size"), - 2: .same(proto: "content"), - ] - - 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.decodeSingularUInt32Field(value: &self.content) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if self.size != 0 { - try visitor.visitSingularInt32Field(value: self.size, fieldNumber: 1) - } - if self.content != 0 { - try visitor.visitSingularUInt32Field(value: self.content, fieldNumber: 2) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: PayloadParameters, rhs: PayloadParameters) -> Bool { - if lhs.size != rhs.size {return false} - if lhs.content != rhs.content {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} - -extension ControlOutput: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { - static let protoMessageName: String = "ControlOutput" - 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.decodeSingularBytesField(value: &self.payload) }() - default: break - } - } - } - - func traverse(visitor: inout V) throws { - if !self.payload.isEmpty { - try visitor.visitSingularBytesField(value: self.payload, fieldNumber: 1) - } - try unknownFields.traverse(visitor: &visitor) - } - - static func ==(lhs: ControlOutput, rhs: ControlOutput) -> Bool { - if lhs.payload != rhs.payload {return false} - if lhs.unknownFields != rhs.unknownFields {return false} - return true - } -} diff --git a/Tests/GRPCHTTP2TransportTests/HTTP2TransportNIOPosixTests.swift b/Tests/GRPCHTTP2TransportTests/HTTP2TransportNIOPosixTests.swift deleted file mode 100644 index 70ae83707..000000000 --- a/Tests/GRPCHTTP2TransportTests/HTTP2TransportNIOPosixTests.swift +++ /dev/null @@ -1,483 +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 XCTest - -#if canImport(NIOSSL) -import NIOSSL -#endif - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -final class HTTP2TransportNIOPosixTests: XCTestCase { - func testGetListeningAddress_IPv4() async throws { - let transport = GRPCHTTP2Core.HTTP2ServerTransport.Posix( - address: .ipv4(host: "0.0.0.0", port: 0), - config: .defaults(transportSecurity: .plaintext) - ) - - try await withThrowingDiscardingTaskGroup { group in - group.addTask { - try await transport.listen { _, _ in } - } - - group.addTask { - let address = try await transport.listeningAddress - let ipv4Address = try XCTUnwrap(address.ipv4) - XCTAssertNotEqual(ipv4Address.port, 0) - transport.beginGracefulShutdown() - } - } - } - - func testGetListeningAddress_IPv6() async throws { - let transport = GRPCHTTP2Core.HTTP2ServerTransport.Posix( - address: .ipv6(host: "::1", port: 0), - config: .defaults(transportSecurity: .plaintext) - ) - - try await withThrowingDiscardingTaskGroup { group in - group.addTask { - try await transport.listen { _, _ in } - } - - group.addTask { - let address = try await transport.listeningAddress - let ipv6Address = try XCTUnwrap(address.ipv6) - XCTAssertNotEqual(ipv6Address.port, 0) - transport.beginGracefulShutdown() - } - } - } - - func testGetListeningAddress_UnixDomainSocket() async throws { - let transport = GRPCHTTP2Core.HTTP2ServerTransport.Posix( - address: .unixDomainSocket(path: "/tmp/posix-uds-test"), - config: .defaults(transportSecurity: .plaintext) - ) - - try await withThrowingDiscardingTaskGroup { group in - group.addTask { - try await transport.listen { _, _ in } - } - - group.addTask { - let address = try await transport.listeningAddress - XCTAssertEqual( - address.unixDomainSocket, - GRPCHTTP2Core.SocketAddress.UnixDomainSocket(path: "/tmp/posix-uds-test") - ) - transport.beginGracefulShutdown() - } - } - } - - func testGetListeningAddress_Vsock() async throws { - try XCTSkipUnless(self.vsockAvailable(), "Vsock unavailable") - - let transport = GRPCHTTP2Core.HTTP2ServerTransport.Posix( - address: .vsock(contextID: .any, port: .any), - config: .defaults(transportSecurity: .plaintext) - ) - - try await withThrowingDiscardingTaskGroup { group in - group.addTask { - try await transport.listen { _, _ in } - } - - group.addTask { - let address = try await transport.listeningAddress - XCTAssertNotNil(address.virtualSocket) - transport.beginGracefulShutdown() - } - } - } - - func testGetListeningAddress_InvalidAddress() async { - let transport = GRPCHTTP2Core.HTTP2ServerTransport.Posix( - address: .unixDomainSocket(path: "/this/should/be/an/invalid/path"), - config: .defaults(transportSecurity: .plaintext) - ) - - try? await withThrowingDiscardingTaskGroup { group in - group.addTask { - try await transport.listen { _, _ in } - } - - group.addTask { - do { - _ = try await transport.listeningAddress - XCTFail("Should have thrown a RuntimeError") - } catch let error as RuntimeError { - XCTAssertEqual(error.code, .serverIsStopped) - XCTAssertEqual( - error.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. - """ - ) - } - } - } - } - - func testGetListeningAddress_StoppedListening() async throws { - let transport = GRPCHTTP2Core.HTTP2ServerTransport.Posix( - address: .ipv4(host: "0.0.0.0", port: 0), - config: .defaults(transportSecurity: .plaintext) - ) - - try? await withThrowingDiscardingTaskGroup { group in - group.addTask { - try await transport.listen { _, _ in } - - do { - _ = try await transport.listeningAddress - XCTFail("Should have thrown a RuntimeError") - } catch let error as RuntimeError { - XCTAssertEqual(error.code, .serverIsStopped) - XCTAssertEqual( - error.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. - """ - ) - } - } - - group.addTask { - let address = try await transport.listeningAddress - XCTAssertNotNil(address.ipv4) - transport.beginGracefulShutdown() - } - } - } - - func testServerConfig_Defaults() throws { - let grpcConfig = HTTP2ServerTransport.Posix.Config.defaults( - transportSecurity: .plaintext - ) - - XCTAssertEqual(grpcConfig.compression, HTTP2ServerTransport.Config.Compression.defaults) - XCTAssertEqual(grpcConfig.connection, HTTP2ServerTransport.Config.Connection.defaults) - XCTAssertEqual(grpcConfig.http2, HTTP2ServerTransport.Config.HTTP2.defaults) - XCTAssertEqual(grpcConfig.rpc, HTTP2ServerTransport.Config.RPC.defaults) - } - - func testClientConfig_Defaults() throws { - let grpcConfig = HTTP2ClientTransport.Posix.Config.defaults( - transportSecurity: .plaintext - ) - - XCTAssertEqual(grpcConfig.compression, HTTP2ClientTransport.Config.Compression.defaults) - XCTAssertEqual(grpcConfig.connection, HTTP2ClientTransport.Config.Connection.defaults) - XCTAssertEqual(grpcConfig.http2, HTTP2ClientTransport.Config.HTTP2.defaults) - XCTAssertEqual(grpcConfig.backoff, HTTP2ClientTransport.Config.Backoff.defaults) - } - - #if canImport(NIOSSL) - static let samplePemCert = """ - -----BEGIN CERTIFICATE----- - MIIGGzCCBAOgAwIBAgIJAJ/X0Fo0ynmEMA0GCSqGSIb3DQEBCwUAMIGjMQswCQYD - VQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5z - b2t5bzEuMCwGA1UECgwlU2FuIEZyYW5zb2t5byBJbnN0aXR1dGUgb2YgVGVjaG5v - bG9neTEVMBMGA1UECwwMUm9ib3RpY3MgTGFiMSAwHgYDVQQDDBdyb2JvdHMuc2Fu - ZnJhbnNva3lvLmVkdTAeFw0xNzEwMTYyMTAxMDJaFw00NzEwMDkyMTAxMDJaMIGj - MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2Fu - IEZyYW5zb2t5bzEuMCwGA1UECgwlU2FuIEZyYW5zb2t5byBJbnN0aXR1dGUgb2Yg - VGVjaG5vbG9neTEVMBMGA1UECwwMUm9ib3RpY3MgTGFiMSAwHgYDVQQDDBdyb2Jv - dHMuc2FuZnJhbnNva3lvLmVkdTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC - ggIBAO9rzJOOE8cmsIqAJMCrHDxkBAMgZhMsJ863MnWtVz5JIJK6CKI/Nu26tEzo - kHy3EI9565RwikvauheMsWaTFA4PD/P+s1DtxRCGIcK5x+SoTN7Drn5ZueoJNZRf - TYuN+gwyhprzrZrYjXpvEVPYuSIeUqK5XGrTyFA2uGj9wY3f9IF4rd7JT0ewRb1U - 8OcR7xQbXKGjkY4iJE1TyfmIsBZboKaG/aYa9KbnWyTkDssaELWUIKrjwwuPgVgS - vlAYmo12MlsGEzkO9z78jvFmhUOsaEldM8Ua2AhOKW0oSYgauVuro/Ap/o5zn8PD - IDapl9g+5vjN2LucqX2a9utoFvxSKXT4NvfpL9fJvzdBNMM4xpqtHIkV0fkiMbWk - EW2FFlOXKnIJV8wT4a9iduuIDMg8O7oc+gt9pG9MHTWthXm4S29DARTqfZ48bW77 - z8RrEURV03o05b/twuAJSRyyOCUi61yMo3YNytebjY2W3Pxqpq+YmT5qhqBZDLlT - LMptuFdISv6SQgg7JoFHGMWRXUavMj/sn5qZD4pQyZToHJ2Vtg5W/MI1pKwc3oKD - 6M3/7Gf35r92V/ox6XT7+fnEsAH8AtQiZJkEbvzJ5lpUihSIaV3a/S+jnk7Lw8Tp - vjtpfjOg+wBblc38Oa9tk2WdXwYDbnvbeL26WmyHwQTUBi1jAgMBAAGjUDBOMB0G - A1UdDgQWBBToPRmTBQEF5F5LcPiUI5qBNPBU+DAfBgNVHSMEGDAWgBToPRmTBQEF - 5F5LcPiUI5qBNPBU+DAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQCY - gxM5lufF2lTB9sH0s1E1VTERv37qoapNP+aw06oZkAD67QOTXFzbsM3JU1diY6rV - Y0g9CLzRO7gZY+kmi1WWnsYiMMSIGjIfsB8S+ot43LME+AJXPVeDZQnoZ6KQ/9r+ - 71Umi4AKLoZ9dInyUIM3EHg9pg5B0eEINrh4J+OPGtlC3NMiWxdmIkZwzfXa+64Z - 8k5aX5piMTI+9BQSMWw5l7tFT/PISuI8b/Ln4IUBXKA0xkONXVnjPOmS0h7MBoc2 - EipChDKnK+Mtm9GQewOCKdS2nsrCndGkIBnUix4ConUYIoywVzWGMD+9OzKNg76d - O6A7MxdjEdKhf1JDvklxInntDUDTlSFL4iEFELwyRseoTzj8vJE+cL6h6ClasYQ6 - p0EeL3UpICYerfIvPhohftCivCH3k7Q1BSf0fq73cQ55nrFAHrqqYjD7HBeBS9hn - 3L6bz9Eo6U9cuxX42k3l1N44BmgcDPin0+CRTirEmahUMb3gmvoSZqQ3Cz86GkIg - 7cNJosc9NyevQlU9SX3ptEbv33tZtlB5GwgZ2hiGBTY0C3HaVFjLpQiSS5ygZLgI - /+AKtah7sTHIAtpUH1ZZEgKPl1Hg6J4x/dBkuk3wxPommNHaYaHREXF+fHMhBrSi - yH8agBmmECpa21SVnr7vrL+KSqfuF+GxwjSNsSR4SA== - -----END CERTIFICATE----- - """ - - static let samplePemKey = """ - -----BEGIN RSA PRIVATE KEY----- - MIIJKAIBAAKCAgEA72vMk44TxyawioAkwKscPGQEAyBmEywnzrcyda1XPkkgkroI - oj827bq0TOiQfLcQj3nrlHCKS9q6F4yxZpMUDg8P8/6zUO3FEIYhwrnH5KhM3sOu - flm56gk1lF9Ni436DDKGmvOtmtiNem8RU9i5Ih5SorlcatPIUDa4aP3Bjd/0gXit - 3slPR7BFvVTw5xHvFBtcoaORjiIkTVPJ+YiwFlugpob9phr0pudbJOQOyxoQtZQg - quPDC4+BWBK+UBiajXYyWwYTOQ73PvyO8WaFQ6xoSV0zxRrYCE4pbShJiBq5W6uj - 8Cn+jnOfw8MgNqmX2D7m+M3Yu5ypfZr262gW/FIpdPg29+kv18m/N0E0wzjGmq0c - iRXR+SIxtaQRbYUWU5cqcglXzBPhr2J264gMyDw7uhz6C32kb0wdNa2FebhLb0MB - FOp9njxtbvvPxGsRRFXTejTlv+3C4AlJHLI4JSLrXIyjdg3K15uNjZbc/Gqmr5iZ - PmqGoFkMuVMsym24V0hK/pJCCDsmgUcYxZFdRq8yP+yfmpkPilDJlOgcnZW2Dlb8 - wjWkrBzegoPozf/sZ/fmv3ZX+jHpdPv5+cSwAfwC1CJkmQRu/MnmWlSKFIhpXdr9 - L6OeTsvDxOm+O2l+M6D7AFuVzfw5r22TZZ1fBgNue9t4vbpabIfBBNQGLWMCAwEA - AQKCAgArWV9PEBhwpIaubQk6gUC5hnpbfpA8xG/os67FM79qHZ9yMZDCn6N4Y6el - jS4sBpFPCQoodD/2AAJVpTmxksu8x+lhiio5avOVTFPsh+qzce2JH/EGG4TX5Rb4 - aFEIBYrSjotknt49/RuQoW+HuOO8U7UulVUwWmwYae/1wow6/eOtVYZVoilil33p - C+oaTFr3TwT0l0MRcwkTnyogrikDw09RF3vxiUvmtFkCUvCCwZNo7QsFJfv4qeEH - a01d/zZsiowPgwgT+qu1kdDn0GIsoJi5P9DRzUx0JILHqtW1ePE6sdca8t+ON00k - Cr5YZ1iA5NK5Fbw6K+FcRqSSduRCLYXAnI5GH1zWMki5TUdl+psvCnpdZK5wysGe - tYfIbrVHXIlg7J3R4BrbMF4q3HwOppTHMrqsGyRVCCSjDwXjreugInV0CRzlapDs - JNEVyrbt6Ild6ie7c1AJqTpibJ9lVYRVpG35Dni9RJy5Uk5m89uWnF9PCjCRCHOf - 4UATY+qie6wlu0E8y43LcTvDi8ROXQQoCnys2ES8DmS+GKJ1uzG1l8jx3jF9BMAJ - kyzZfSmPwuS2NUk8sftYQ8neJSgk4DOV4h7x5ghaBWYzseomy3uo3gD4IyuiO56K - y7IYZnXSt2s8LfzhVcB5I4IZbSIvP/MAEkGMC09SV+dEcEJSQQKCAQEA/uJex1ef - g+q4gb/C4/biPr+ZRFheVuHu49ES0DXxoxmTbosGRDPRFBLwtPxCLuzHXa1Du2Vc - c0E12zLy8wNczv5bGAxynPo57twJCyeptFNFJkb+0uxRrCi+CZ56Qertg2jr460Q - cg+TMYxauDleLzR7uwL6VnOhTSq3CVTA2TrQ+kjIHgVqmmpwgk5bPBRDj2EuqdyD - dEQmt4z/0fFFBmW6iBcXS9y8Q1rCnAHKjDUEoXKyJYL85szupjUuerOt6iTIe7CJ - pH0REwQO4djwM4Ju/PEGfBs+RqgNXoHmBMcFdf9RdogCuFit7lX0+LlRT/KJitan - LaaFgY1TXTVkcwKCAQEA8HgZuPGVHQTMHCOfNesXxnCY9Dwqa9ZVukqDLMaZ0TVy - PIqXhdNeVCWpP+VXWhj9JRLNuW8VWYMxk+poRmsZgbdwSbq30ljsGlfoupCpXfhd - AIhUeRwLVl4XnaHW+MjAmY/rqO156/LvNbV5e0YsqObzynlTczmhhYwi48x1tdf0 - iuCn8o3+Ikv8xM7MuMnv5QmGp2l8Q3BhwxLN1x4MXfbG+4BGsqavudIkt71RVbSb - Sp7U4Khq3UEnCekrceRLQpJykRFu11/ntPsJ0Q+fLuvuRUMg/wsq8WTuVlwLrw46 - hlRcq6S99jc9j2TbidxHyps6j8SDnEsEFHMHH8THUQKCAQAd03WN1CYZdL0UidEP - hhNhjmAsDD814Yhn5k5SSQ22rUaAWApqrrmXpMPAGgjQnuqRfrX/VtQjtIzN0r91 - Sn5wxnj4bnR3BB0FY4A3avPD4z6jRQmKuxavk7DxRTc/QXN7vipkYRscjdAGq0ru - ZeAsm/Kipq2Oskc81XPHxsAua2CK+TtZr/6ShUQXK34noKNrQs8IF4LWdycksX46 - Hgaawgq65CDYwsLRCuzc/qSqFYYuMlLAavyXMYH3tx9yQlZmoNlJCBaDRhNaa04m - hZFOJcRBGx9MJI/8CqxN09uL0ZJFBZSNz0qqMc5gpnRdKqpmNZZ8xbOYdvUGfPg1 - XwsbAoIBAGdH7iRU/mp8SP48/oC1/HwqmEcuIDo40JE2t6hflGkav3npPLMp2XXi - xxK+egokeXWW4e0nHNBZXM3e+/JixY3FL+E65QDfWGjoIPkgcN3/clJsO3vY47Ww - rAv0GtS3xKEwA1OGy7rfmIZE72xW84+HwmXQPltbAVjOm52jj1sO6eVMIFY5TlGE - uYf+Gkez0+lXchItaEW+2v5h8S7XpRAmkcgrjDHnDcqNy19vXKOm8pvWJDBppZxq - A05qa1J7byekprhP+H9gnbBJsimsv/3zL19oOZ/ROBx98S/+ULZbMh/H1BWUqFI7 - 36Da/L/1cJBAo6JkEPLr9VCjJwgqCEECggEBAI6+35Lf4jDwRPvZV7kE+FQuFp1G - /tKxIJtPOZU3sbOVlsFsOoyEfV6+HbpeWxlWnrOnKRFOLoC3s5MVTjPglu1rC0ZX - 4b0wMetvun5S1MGadB808rvu5EsEB1vznz1vOXV8oDdkdgBiiUcKewSeCrG1IrXy - B9ux859S3JjELzeuNdz+xHqu2AqR22gtqN72tJUEQ95qLGZ8vo+ytY9MDVDqoSWJ - 9pqHXFUVLmwHTM0/pciXN4Kx1IL9FZ3fjXgME0vdYpWYQkcvSKLsswXN+LnYcpoQ - h33H/Kz4yji7jPN6Uk9wMyG7XGqpjYAuKCd6V3HEHUiGJZzho/VBgb3TVnw= - -----END RSA PRIVATE KEY----- - """ - - func testServerTLSConfig_Defaults() throws { - let grpcTLSConfig = HTTP2ServerTransport.Posix.Config.TLS.defaults( - certificateChain: [ - .bytes(Array(Self.samplePemCert.utf8), format: .pem) - ], - privateKey: .bytes(Array(Self.samplePemKey.utf8), format: .pem) - ) - let nioSSLTLSConfig = try TLSConfiguration(grpcTLSConfig) - - XCTAssertEqual( - nioSSLTLSConfig.certificateChain, - [ - .certificate( - try NIOSSLCertificate( - bytes: Array(Self.samplePemCert.utf8), - format: .pem - ) - ) - ] - ) - XCTAssertEqual( - nioSSLTLSConfig.privateKey, - .privateKey(try NIOSSLPrivateKey(bytes: Array(Self.samplePemKey.utf8), format: .pem)) - ) - XCTAssertEqual(nioSSLTLSConfig.minimumTLSVersion, .tlsv12) - XCTAssertEqual(nioSSLTLSConfig.certificateVerification, .none) - XCTAssertEqual(nioSSLTLSConfig.trustRoots, .default) - XCTAssertEqual(nioSSLTLSConfig.applicationProtocols, ["grpc-exp", "h2"]) - } - - func testServerTLSConfig_mTLS() throws { - let grpcTLSConfig = HTTP2ServerTransport.Posix.Config.TLS.mTLS( - certificateChain: [ - .bytes(Array(Self.samplePemCert.utf8), format: .pem) - ], - privateKey: .bytes(Array(Self.samplePemKey.utf8), format: .pem) - ) - let nioSSLTLSConfig = try TLSConfiguration(grpcTLSConfig) - - XCTAssertEqual( - nioSSLTLSConfig.certificateChain, - [ - .certificate( - try NIOSSLCertificate( - bytes: Array(Self.samplePemCert.utf8), - format: .pem - ) - ) - ] - ) - XCTAssertEqual( - nioSSLTLSConfig.privateKey, - .privateKey(try NIOSSLPrivateKey(bytes: Array(Self.samplePemKey.utf8), format: .pem)) - ) - XCTAssertEqual(nioSSLTLSConfig.minimumTLSVersion, .tlsv12) - XCTAssertEqual(nioSSLTLSConfig.certificateVerification, .noHostnameVerification) - XCTAssertEqual(nioSSLTLSConfig.trustRoots, .default) - XCTAssertEqual(nioSSLTLSConfig.applicationProtocols, ["grpc-exp", "h2"]) - } - - func testServerTLSConfig_FullVerifyClient() throws { - var grpcTLSConfig = HTTP2ServerTransport.Posix.Config.TLS.defaults( - certificateChain: [ - .bytes(Array(Self.samplePemCert.utf8), format: .pem) - ], - privateKey: .bytes(Array(Self.samplePemKey.utf8), format: .pem) - ) - grpcTLSConfig.clientCertificateVerification = .fullVerification - let nioSSLTLSConfig = try TLSConfiguration(grpcTLSConfig) - - XCTAssertEqual( - nioSSLTLSConfig.certificateChain, - [ - .certificate( - try NIOSSLCertificate( - bytes: Array(Self.samplePemCert.utf8), - format: .pem - ) - ) - ] - ) - XCTAssertEqual( - nioSSLTLSConfig.privateKey, - .privateKey(try NIOSSLPrivateKey(bytes: Array(Self.samplePemKey.utf8), format: .pem)) - ) - XCTAssertEqual(nioSSLTLSConfig.minimumTLSVersion, .tlsv12) - XCTAssertEqual(nioSSLTLSConfig.certificateVerification, .fullVerification) - XCTAssertEqual(nioSSLTLSConfig.trustRoots, .default) - XCTAssertEqual(nioSSLTLSConfig.applicationProtocols, ["grpc-exp", "h2"]) - } - - func testServerTLSConfig_CustomTrustRoots() throws { - var grpcTLSConfig = HTTP2ServerTransport.Posix.Config.TLS.defaults( - certificateChain: [ - .bytes(Array(Self.samplePemCert.utf8), format: .pem) - ], - privateKey: .bytes(Array(Self.samplePemKey.utf8), format: .pem) - ) - grpcTLSConfig.trustRoots = .certificates([.bytes(Array(Self.samplePemCert.utf8), format: .pem)]) - let nioSSLTLSConfig = try TLSConfiguration(grpcTLSConfig) - - XCTAssertEqual( - nioSSLTLSConfig.certificateChain, - [ - .certificate( - try NIOSSLCertificate( - bytes: Array(Self.samplePemCert.utf8), - format: .pem - ) - ) - ] - ) - XCTAssertEqual( - nioSSLTLSConfig.privateKey, - .privateKey(try NIOSSLPrivateKey(bytes: Array(Self.samplePemKey.utf8), format: .pem)) - ) - XCTAssertEqual(nioSSLTLSConfig.minimumTLSVersion, .tlsv12) - XCTAssertEqual(nioSSLTLSConfig.certificateVerification, .none) - XCTAssertEqual( - nioSSLTLSConfig.trustRoots, - .certificates(try NIOSSLCertificate.fromPEMBytes(Array(Self.samplePemCert.utf8))) - ) - XCTAssertEqual(nioSSLTLSConfig.applicationProtocols, ["grpc-exp", "h2"]) - } - - func testClientTLSConfig_Defaults() throws { - let grpcTLSConfig = HTTP2ClientTransport.Posix.Config.TLS.defaults - let nioSSLTLSConfig = try TLSConfiguration(grpcTLSConfig) - - XCTAssertEqual(nioSSLTLSConfig.certificateChain, []) - XCTAssertNil(nioSSLTLSConfig.privateKey) - XCTAssertEqual(nioSSLTLSConfig.minimumTLSVersion, .tlsv12) - XCTAssertEqual(nioSSLTLSConfig.certificateVerification, .fullVerification) - XCTAssertEqual(nioSSLTLSConfig.trustRoots, .default) - XCTAssertEqual(nioSSLTLSConfig.applicationProtocols, ["grpc-exp", "h2"]) - } - - func testClientTLSConfig_CustomCertificateChainAndPrivateKey() throws { - var grpcTLSConfig = HTTP2ClientTransport.Posix.Config.TLS.defaults - grpcTLSConfig.certificateChain = [ - .bytes(Array(Self.samplePemCert.utf8), format: .pem) - ] - grpcTLSConfig.privateKey = .bytes(Array(Self.samplePemKey.utf8), format: .pem) - let nioSSLTLSConfig = try TLSConfiguration(grpcTLSConfig) - - XCTAssertEqual( - nioSSLTLSConfig.certificateChain, - [ - .certificate( - try NIOSSLCertificate( - bytes: Array(Self.samplePemCert.utf8), - format: .pem - ) - ) - ] - ) - XCTAssertEqual( - nioSSLTLSConfig.privateKey, - .privateKey(try NIOSSLPrivateKey(bytes: Array(Self.samplePemKey.utf8), format: .pem)) - ) - XCTAssertEqual(nioSSLTLSConfig.minimumTLSVersion, .tlsv12) - XCTAssertEqual(nioSSLTLSConfig.certificateVerification, .fullVerification) - XCTAssertEqual(nioSSLTLSConfig.trustRoots, .default) - XCTAssertEqual(nioSSLTLSConfig.applicationProtocols, ["grpc-exp", "h2"]) - } - - func testClientTLSConfig_CustomTrustRoots() throws { - var grpcTLSConfig = HTTP2ClientTransport.Posix.Config.TLS.defaults - grpcTLSConfig.trustRoots = .certificates([.bytes(Array(Self.samplePemCert.utf8), format: .pem)]) - let nioSSLTLSConfig = try TLSConfiguration(grpcTLSConfig) - - XCTAssertEqual(nioSSLTLSConfig.certificateChain, []) - XCTAssertNil(nioSSLTLSConfig.privateKey) - XCTAssertEqual(nioSSLTLSConfig.minimumTLSVersion, .tlsv12) - XCTAssertEqual(nioSSLTLSConfig.certificateVerification, .fullVerification) - XCTAssertEqual( - nioSSLTLSConfig.trustRoots, - .certificates(try NIOSSLCertificate.fromPEMBytes(Array(Self.samplePemCert.utf8))) - ) - XCTAssertEqual(nioSSLTLSConfig.applicationProtocols, ["grpc-exp", "h2"]) - } - - func testClientTLSConfig_CustomCertificateVerification() throws { - var grpcTLSConfig = HTTP2ClientTransport.Posix.Config.TLS.defaults - grpcTLSConfig.serverCertificateVerification = .noHostnameVerification - let nioSSLTLSConfig = try TLSConfiguration(grpcTLSConfig) - - XCTAssertEqual(nioSSLTLSConfig.certificateChain, []) - XCTAssertNil(nioSSLTLSConfig.privateKey) - XCTAssertEqual(nioSSLTLSConfig.minimumTLSVersion, .tlsv12) - XCTAssertEqual(nioSSLTLSConfig.certificateVerification, .noHostnameVerification) - XCTAssertEqual(nioSSLTLSConfig.trustRoots, .default) - XCTAssertEqual(nioSSLTLSConfig.applicationProtocols, ["grpc-exp", "h2"]) - } - #endif -} diff --git a/Tests/GRPCHTTP2TransportTests/HTTP2TransportNIOTransportServicesTests.swift b/Tests/GRPCHTTP2TransportTests/HTTP2TransportNIOTransportServicesTests.swift deleted file mode 100644 index 2afe9e9fa..000000000 --- a/Tests/GRPCHTTP2TransportTests/HTTP2TransportNIOTransportServicesTests.swift +++ /dev/null @@ -1,235 +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) -import GRPCCore -import GRPCHTTP2Core -import GRPCHTTP2TransportNIOTransportServices -import XCTest - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -final class HTTP2TransportNIOTransportServicesTests: XCTestCase { - private static let p12bundleURL = URL(fileURLWithPath: #filePath) - .deletingLastPathComponent() // (this file) - .deletingLastPathComponent() // GRPCHTTP2TransportTests - .deletingLastPathComponent() // Tests - .appendingPathComponent("Sources") - .appendingPathComponent("GRPCSampleData") - .appendingPathComponent("bundle") - .appendingPathExtension("p12") - - @Sendable private static func loadIdentity() throws -> SecIdentity { - let data = try Data(contentsOf: Self.p12bundleURL) - - var externalFormat = SecExternalFormat.formatUnknown - var externalItemType = SecExternalItemType.itemTypeUnknown - let passphrase = "password" as CFTypeRef - var exportKeyParams = SecItemImportExportKeyParameters() - exportKeyParams.passphrase = Unmanaged.passUnretained(passphrase) - var items: CFArray? - - let status = SecItemImport( - data as CFData, - "bundle.p12" as CFString, - &externalFormat, - &externalItemType, - SecItemImportExportFlags(rawValue: 0), - &exportKeyParams, - nil, - &items - ) - - if status != errSecSuccess { - XCTFail( - """ - Unable to load identity from '\(Self.p12bundleURL)'. \ - SecItemImport failed with status \(status) - """ - ) - } else if items == nil { - XCTFail( - """ - Unable to load identity from '\(Self.p12bundleURL)'. \ - SecItemImport failed. - """ - ) - } - - return ((items! as NSArray)[0] as! SecIdentity) - } - - func testGetListeningAddress_IPv4() async throws { - let transport = GRPCHTTP2Core.HTTP2ServerTransport.TransportServices( - address: .ipv4(host: "0.0.0.0", port: 0), - config: .defaults(transportSecurity: .plaintext) - ) - - try await withThrowingDiscardingTaskGroup { group in - group.addTask { - try await transport.listen { _, _ in } - } - - group.addTask { - let address = try await transport.listeningAddress - let ipv4Address = try XCTUnwrap(address.ipv4) - XCTAssertNotEqual(ipv4Address.port, 0) - transport.beginGracefulShutdown() - } - } - } - - func testGetListeningAddress_IPv6() async throws { - let transport = GRPCHTTP2Core.HTTP2ServerTransport.TransportServices( - address: .ipv6(host: "::1", port: 0), - config: .defaults(transportSecurity: .plaintext) - ) - - try await withThrowingDiscardingTaskGroup { group in - group.addTask { - try await transport.listen { _, _ in } - } - - group.addTask { - let address = try await transport.listeningAddress - let ipv6Address = try XCTUnwrap(address.ipv6) - XCTAssertNotEqual(ipv6Address.port, 0) - transport.beginGracefulShutdown() - } - } - } - - func testGetListeningAddress_UnixDomainSocket() async throws { - let transport = GRPCHTTP2Core.HTTP2ServerTransport.TransportServices( - address: .unixDomainSocket(path: "/tmp/niots-uds-test"), - config: .defaults(transportSecurity: .plaintext) - ) - defer { - // NIOTS does not unlink the UDS on close. - try? FileManager.default.removeItem(atPath: "/tmp/niots-uds-test") - } - - try await withThrowingDiscardingTaskGroup { group in - group.addTask { - try await transport.listen { _, _ in } - } - - group.addTask { - let address = try await transport.listeningAddress - XCTAssertEqual( - address.unixDomainSocket, - GRPCHTTP2Core.SocketAddress.UnixDomainSocket(path: "/tmp/niots-uds-test") - ) - transport.beginGracefulShutdown() - } - } - } - - func testGetListeningAddress_InvalidAddress() async { - let transport = GRPCHTTP2Core.HTTP2ServerTransport.TransportServices( - address: .unixDomainSocket(path: "/this/should/be/an/invalid/path"), - config: .defaults(transportSecurity: .plaintext) - ) - - try? await withThrowingDiscardingTaskGroup { group in - group.addTask { - try await transport.listen { _, _ in } - } - - group.addTask { - do { - _ = try await transport.listeningAddress - XCTFail("Should have thrown a RuntimeError") - } catch let error as RuntimeError { - XCTAssertEqual(error.code, .serverIsStopped) - XCTAssertEqual( - error.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. - """ - ) - } - } - } - } - - func testGetListeningAddress_StoppedListening() async throws { - let transport = GRPCHTTP2Core.HTTP2ServerTransport.TransportServices( - address: .ipv4(host: "0.0.0.0", port: 0), - config: .defaults(transportSecurity: .plaintext) - ) - - try? await withThrowingDiscardingTaskGroup { group in - group.addTask { - try await transport.listen { _, _ in } - - do { - _ = try await transport.listeningAddress - XCTFail("Should have thrown a RuntimeError") - } catch let error as RuntimeError { - XCTAssertEqual(error.code, .serverIsStopped) - XCTAssertEqual( - error.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. - """ - ) - } - } - - group.addTask { - let address = try await transport.listeningAddress - XCTAssertNotNil(address.ipv4) - transport.beginGracefulShutdown() - } - } - } - - func testServerConfig_Defaults() throws { - let identityProvider = Self.loadIdentity - let grpcTLSConfig = HTTP2ServerTransport.TransportServices.Config.TLS.defaults( - identityProvider: identityProvider - ) - let grpcConfig = HTTP2ServerTransport.TransportServices.Config.defaults( - transportSecurity: .tls(grpcTLSConfig) - ) - - XCTAssertEqual(grpcConfig.compression, HTTP2ServerTransport.Config.Compression.defaults) - XCTAssertEqual(grpcConfig.connection, HTTP2ServerTransport.Config.Connection.defaults) - XCTAssertEqual(grpcConfig.http2, HTTP2ServerTransport.Config.HTTP2.defaults) - XCTAssertEqual(grpcConfig.rpc, HTTP2ServerTransport.Config.RPC.defaults) - XCTAssertEqual(try grpcTLSConfig.identityProvider(), try identityProvider()) - XCTAssertEqual(grpcTLSConfig.requireALPN, false) - } - - func testClientConfig_Defaults() throws { - let identityProvider = Self.loadIdentity - let grpcTLSConfig = HTTP2ClientTransport.TransportServices.Config.TLS( - identityProvider: identityProvider - ) - let grpcConfig = HTTP2ClientTransport.TransportServices.Config.defaults( - transportSecurity: .tls(grpcTLSConfig) - ) - - XCTAssertEqual(grpcConfig.compression, HTTP2ClientTransport.Config.Compression.defaults) - XCTAssertEqual(grpcConfig.connection, HTTP2ClientTransport.Config.Connection.defaults) - XCTAssertEqual(grpcConfig.http2, HTTP2ClientTransport.Config.HTTP2.defaults) - XCTAssertEqual(grpcConfig.backoff, HTTP2ClientTransport.Config.Backoff.defaults) - XCTAssertEqual(try grpcTLSConfig.identityProvider(), try identityProvider()) - } -} -#endif diff --git a/Tests/GRPCHTTP2TransportTests/HTTP2TransportTests.swift b/Tests/GRPCHTTP2TransportTests/HTTP2TransportTests.swift deleted file mode 100644 index 108cc709a..000000000 --- a/Tests/GRPCHTTP2TransportTests/HTTP2TransportTests.swift +++ /dev/null @@ -1,1507 +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 GRPCHTTP2TransportNIOTransportServices -import GRPCProtobuf -import XCTest - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -final class HTTP2TransportTests: XCTestCase { - // A combination of client and server transport kinds. - struct Transport: Sendable, CustomStringConvertible { - var server: Kind - var client: Kind - - enum Kind: Sendable, CustomStringConvertible { - case posix - case niots - - var description: String { - switch self { - case .posix: - return "NIOPosix" - case .niots: - return "NIOTS" - } - } - } - - var description: String { - "server=\(self.server) client=\(self.client)" - } - } - - func forEachTransportPair( - _ transport: [Transport] = .supported, - enableControlService: Bool = true, - clientCompression: CompressionAlgorithm = .none, - clientEnabledCompression: CompressionAlgorithmSet = .none, - serverCompression: CompressionAlgorithmSet = .none, - _ execute: (ControlClient, Transport) async throws -> Void - ) async throws { - for pair in transport { - try await withThrowingTaskGroup(of: Void.self) { group in - let (server, address) = try await self.runServer( - in: &group, - kind: pair.server, - enableControlService: enableControlService, - compression: serverCompression - ) - - let target: any ResolvableTarget - if let ipv4 = address.ipv4 { - target = .ipv4(host: ipv4.host, port: ipv4.port) - } else if let ipv6 = address.ipv6 { - target = .ipv6(host: ipv6.host, port: ipv6.port) - } else if let uds = address.unixDomainSocket { - target = .unixDomainSocket(path: uds.path) - } else { - XCTFail("Unexpected address to connect to") - return - } - - let client = try self.makeClient( - kind: pair.client, - target: target, - compression: clientCompression, - enabledCompression: clientEnabledCompression - ) - - group.addTask { - try await client.run() - } - - do { - let control = ControlClient(wrapping: client) - try await execute(control, pair) - } catch { - XCTFail("Unexpected error: '\(error)' (\(pair))") - } - - server.beginGracefulShutdown() - client.beginGracefulShutdown() - } - } - } - - func forEachClientAndHTTPStatusCodeServer( - _ kind: [Transport.Kind] = [.posix, .niots], - _ execute: (ControlClient, Transport.Kind) async throws -> Void - ) async throws { - for clientKind in kind { - try await withThrowingTaskGroup(of: Void.self) { group in - let server = HTTP2StatusCodeServer() - group.addTask { - try await server.run() - } - - let address = try await server.listeningAddress - let client = try self.makeClient( - kind: clientKind, - target: .ipv4(host: address.host, port: address.port), - compression: .none, - enabledCompression: .none - ) - group.addTask { - try await client.run() - } - - do { - let control = ControlClient(wrapping: client) - try await execute(control, clientKind) - } catch { - XCTFail("Unexpected error: '\(error)' (\(clientKind))") - } - - group.cancelAll() - } - } - } - - private func runServer( - in group: inout ThrowingTaskGroup, - kind: Transport.Kind, - enableControlService: Bool, - compression: CompressionAlgorithmSet - ) async throws -> (GRPCServer, GRPCHTTP2Core.SocketAddress) { - let services = enableControlService ? [ControlService()] : [] - - switch kind { - case .posix: - let server = GRPCServer( - transport: .http2NIOPosix( - address: .ipv4(host: "127.0.0.1", port: 0), - config: .defaults(transportSecurity: .plaintext) { - $0.compression.enabledAlgorithms = compression - } - ), - services: services - ) - - group.addTask { - try await server.serve() - } - - let address = try await server.listeningAddress! - return (server, address) - - case .niots: - #if canImport(Network) - let server = GRPCServer( - transport: .http2NIOTS( - address: .ipv4(host: "127.0.0.1", port: 0), - config: .defaults(transportSecurity: .plaintext) { - $0.compression.enabledAlgorithms = compression - } - ), - services: services - ) - - group.addTask { - try await server.serve() - } - - let address = try await server.listeningAddress! - return (server, address) - #else - throw XCTSkip("Transport not supported on this platform") - #endif - } - } - - private func makeClient( - kind: Transport.Kind, - target: any ResolvableTarget, - compression: CompressionAlgorithm, - enabledCompression: CompressionAlgorithmSet - ) throws -> GRPCClient { - let transport: any ClientTransport - - switch kind { - case .posix: - var serviceConfig = ServiceConfig() - serviceConfig.loadBalancingConfig = [.roundRobin] - transport = try HTTP2ClientTransport.Posix( - target: target, - config: .defaults(transportSecurity: .plaintext) { - $0.compression.algorithm = compression - $0.compression.enabledAlgorithms = enabledCompression - }, - serviceConfig: serviceConfig - ) - - case .niots: - #if canImport(Network) - var serviceConfig = ServiceConfig() - serviceConfig.loadBalancingConfig = [.roundRobin] - transport = try HTTP2ClientTransport.TransportServices( - target: target, - config: .defaults(transportSecurity: .plaintext) { - $0.compression.algorithm = compression - $0.compression.enabledAlgorithms = enabledCompression - }, - serviceConfig: serviceConfig - ) - #else - throw XCTSkip("Transport not supported on this platform") - #endif - } - - return GRPCClient(transport: transport) - } - - func testUnaryOK() async throws { - // Client sends one message, server sends back metadata, a single message, and an ok status with - // trailing metadata. - try await self.forEachTransportPair { control, pair in - let input = ControlInput.with { - $0.echoMetadataInHeaders = true - $0.echoMetadataInTrailers = true - $0.numberOfMessages = 1 - $0.messageParams = .with { - $0.content = 0 - $0.size = 1024 - } - } - - let metadata: Metadata = ["test-key": "test-value"] - let request = ClientRequest.Single(message: input, metadata: metadata) - - try await control.unary(request: request) { response in - let message = try response.message - XCTAssertEqual(message.payload, Data(repeating: 0, count: 1024), "\(pair)") - - let initial = response.metadata - XCTAssertEqual(Array(initial["echo-test-key"]), ["test-value"], "\(pair)") - - let trailing = response.trailingMetadata - XCTAssertEqual(Array(trailing["echo-test-key"]), ["test-value"], "\(pair)") - } - } - } - - func testUnaryNotOK() async throws { - // Client sends one message, server sends back metadata, a single message, and a non-ok status - // with trailing metadata. - try await self.forEachTransportPair { control, pair in - let input = ControlInput.with { - $0.echoMetadataInTrailers = true - $0.numberOfMessages = 1 - $0.messageParams = .with { - $0.content = 0 - $0.size = 1024 - } - $0.status = .with { - $0.code = .aborted - $0.message = "\(#function)" - } - } - - let metadata: Metadata = ["test-key": "test-value"] - let request = ClientRequest.Single(message: input, metadata: metadata) - - try await control.unary(request: request) { response in - XCTAssertThrowsError(ofType: RPCError.self, try response.message) { error in - XCTAssertEqual(error.code, .aborted) - XCTAssertEqual(error.message, "\(#function)") - - let trailing = error.metadata - XCTAssertEqual(Array(trailing["echo-test-key"]), ["test-value"], "\(pair)") - } - - let trailing = response.trailingMetadata - XCTAssertEqual(Array(trailing["echo-test-key"]), ["test-value"], "\(pair)") - } - } - } - - func testUnaryRejected() async throws { - // Client sends one message, server sends non-ok status with trailing metadata. - try await self.forEachTransportPair { control, pair in - let metadata: Metadata = ["test-key": "test-value"] - let request = ClientRequest.Single( - message: .trailersOnly(code: .aborted, message: "\(#function)", echoMetadata: true), - metadata: metadata - ) - - try await control.unary(request: request) { response in - XCTAssertThrowsError(ofType: RPCError.self, try response.message) { error in - XCTAssertEqual(error.code, .aborted, "\(pair)") - XCTAssertEqual(error.message, "\(#function)", "\(pair)") - - let trailing = error.metadata - XCTAssertEqual(Array(trailing["echo-test-key"]), ["test-value"], "\(pair)") - } - - // No initial metadata for trailers-only. - XCTAssertEqual(response.metadata, [:]) - - let trailing = response.trailingMetadata - XCTAssertEqual(Array(trailing["echo-test-key"]), ["test-value"], "\(pair)") - } - } - } - - func testClientStreamingOK() async throws { - try await self.forEachTransportPair { control, pair in - let metadata: Metadata = ["test-key": "test-value"] - let request = ClientRequest.Stream( - of: ControlInput.self, - metadata: metadata - ) { writer in - try await writer.write(.echoMetadata) - // Send a few messages which are ignored. - try await writer.write(.noOp) - try await writer.write(.noOp) - try await writer.write(.noOp) - // Send a message. - try await writer.write(.messages(1, repeating: 42, count: 1024)) - // ... and the final status. - try await writer.write(.status(code: .ok, message: "", echoMetadata: true)) - } - - try await control.clientStream(request: request) { response in - let message = try response.message - XCTAssertEqual(message.payload, Data(repeating: 42, count: 1024), "\(pair)") - - let initial = response.metadata - XCTAssertEqual(Array(initial["echo-test-key"]), ["test-value"], "\(pair)") - - let trailing = response.trailingMetadata - XCTAssertEqual(Array(trailing["echo-test-key"]), ["test-value"], "\(pair)") - } - } - } - - func testClientStreamingNotOK() async throws { - try await self.forEachTransportPair { control, pair in - let metadata: Metadata = ["test-key": "test-value"] - let request = ClientRequest.Stream( - of: ControlInput.self, - metadata: metadata - ) { writer in - try await writer.write(.echoMetadata) - // Send a few messages which are ignored. - try await writer.write(.noOp) - try await writer.write(.noOp) - try await writer.write(.noOp) - // Send a message. - try await writer.write(.messages(1, repeating: 42, count: 1024)) - // Send the final status. - try await writer.write(.status(code: .aborted, message: "\(#function)", echoMetadata: true)) - } - - try await control.clientStream(request: request) { response in - XCTAssertThrowsError(ofType: RPCError.self, try response.message) { error in - XCTAssertEqual(error.code, .aborted, "\(pair)") - XCTAssertEqual(error.message, "\(#function)", "\(pair)") - - let trailing = error.metadata - XCTAssertEqual(Array(trailing["echo-test-key"]), ["test-value"], "\(pair)") - } - - let initial = response.metadata - XCTAssertEqual(Array(initial["echo-test-key"]), ["test-value"], "\(pair)") - - let trailing = response.trailingMetadata - XCTAssertEqual(Array(trailing["echo-test-key"]), ["test-value"], "\(pair)") - } - } - } - - func testClientStreamingRejected() async throws { - // Client sends one message, server sends non-ok status with trailing metadata. - try await self.forEachTransportPair { control, pair in - let metadata: Metadata = ["test-key": "test-value"] - let request = ClientRequest.Stream( - of: ControlInput.self, - metadata: metadata - ) { writer in - let message: ControlInput = .trailersOnly( - code: .aborted, - message: "\(#function)", - echoMetadata: true - ) - - try await writer.write(message) - } - - try await control.clientStream(request: request) { response in - XCTAssertThrowsError(ofType: RPCError.self, try response.message) { error in - XCTAssertEqual(error.code, .aborted, "\(pair)") - XCTAssertEqual(error.message, "\(#function)", "\(pair)") - - let trailing = error.metadata - XCTAssertEqual(Array(trailing["echo-test-key"]), ["test-value"], "\(pair)") - } - - // No initial metadata for trailers-only. - XCTAssertEqual(response.metadata, [:]) - - let trailing = response.trailingMetadata - XCTAssertEqual(Array(trailing["echo-test-key"]), ["test-value"], "\(pair)") - } - } - } - - func testServerStreamingOK() async throws { - try await self.forEachTransportPair { control, pair in - let metadata: Metadata = ["test-key": "test-value"] - let input = ControlInput.with { - $0.echoMetadataInHeaders = true - $0.echoMetadataInTrailers = true - $0.numberOfMessages = 5 - $0.messageParams = .with { - $0.content = 42 - $0.size = 1024 - } - } - - let request = ClientRequest.Single(message: input, metadata: metadata) - try await control.serverStream(request: request) { response in - switch response.accepted { - case .success(let contents): - XCTAssertEqual(Array(contents.metadata["echo-test-key"]), ["test-value"], "\(pair)") - - var messagesReceived = 0 - for try await part in contents.bodyParts { - switch part { - case .message(let message): - messagesReceived += 1 - XCTAssertEqual(message.payload, Data(repeating: 42, count: 1024)) - case .trailingMetadata(let metadata): - XCTAssertEqual(Array(metadata["echo-test-key"]), ["test-value"], "\(pair)") - } - } - - XCTAssertEqual(messagesReceived, 5) - - case .failure(let error): - throw error - } - } - } - } - - func testServerStreamingEmptyOK() async throws { - try await self.forEachTransportPair { control, pair in - let metadata: Metadata = ["test-key": "test-value"] - // Echo back metadata, but don't send any messages. - let input = ControlInput.with { - $0.echoMetadataInHeaders = true - $0.echoMetadataInTrailers = true - } - - let request = ClientRequest.Single(message: input, metadata: metadata) - try await control.serverStream(request: request) { response in - switch response.accepted { - case .success(let contents): - XCTAssertEqual(Array(contents.metadata["echo-test-key"]), ["test-value"], "\(pair)") - - for try await part in contents.bodyParts { - switch part { - case .message: - XCTFail("Unexpected message") - case .trailingMetadata(let metadata): - XCTAssertEqual(Array(metadata["echo-test-key"]), ["test-value"], "\(pair)") - } - } - - case .failure(let error): - throw error - } - } - } - } - - func testServerStreamingNotOK() async throws { - try await self.forEachTransportPair { control, pair in - let metadata: Metadata = ["test-key": "test-value"] - let input = ControlInput.with { - $0.echoMetadataInHeaders = true - $0.echoMetadataInTrailers = true - $0.numberOfMessages = 5 - $0.messageParams = .with { - $0.content = 42 - $0.size = 1024 - } - $0.status = .with { - $0.code = .aborted - $0.message = "\(#function)" - } - } - - let request = ClientRequest.Single(message: input, metadata: metadata) - try await control.serverStream(request: request) { response in - switch response.accepted { - case .success(let contents): - XCTAssertEqual(Array(contents.metadata["echo-test-key"]), ["test-value"], "\(pair)") - - var messagesReceived = 0 - do { - for try await part in contents.bodyParts { - switch part { - case .message(let message): - messagesReceived += 1 - XCTAssertEqual(message.payload, Data(repeating: 42, count: 1024)) - case .trailingMetadata: - XCTFail("Unexpected trailing metadata, should be provided in RPCError") - } - } - XCTFail("Expected error to be thrown") - } catch let error as RPCError { - XCTAssertEqual(error.code, .aborted) - XCTAssertEqual(error.message, "\(#function)") - XCTAssertEqual(Array(error.metadata["echo-test-key"]), ["test-value"], "\(pair)") - } - - XCTAssertEqual(messagesReceived, 5) - - case .failure(let error): - throw error - } - } - } - } - - func testServerStreamingEmptyNotOK() async throws { - try await self.forEachTransportPair { control, pair in - let metadata: Metadata = ["test-key": "test-value"] - let input = ControlInput.with { - $0.echoMetadataInHeaders = true - $0.echoMetadataInTrailers = true - $0.status = .with { - $0.code = .aborted - $0.message = "\(#function)" - } - } - - let request = ClientRequest.Single(message: input, metadata: metadata) - try await control.serverStream(request: request) { response in - switch response.accepted { - case .success(let contents): - XCTAssertEqual(Array(contents.metadata["echo-test-key"]), ["test-value"], "\(pair)") - - do { - for try await _ in contents.bodyParts { - XCTFail("Unexpected message, \(pair)") - } - XCTFail("Expected error to be thrown") - } catch let error as RPCError { - XCTAssertEqual(error.code, .aborted) - XCTAssertEqual(error.message, "\(#function)") - XCTAssertEqual(Array(error.metadata["echo-test-key"]), ["test-value"], "\(pair)") - } - - case .failure(let error): - throw error - } - } - } - } - - func testServerStreamingRejected() async throws { - try await self.forEachTransportPair { control, pair in - let metadata: Metadata = ["test-key": "test-value"] - let request = ClientRequest.Single( - message: .trailersOnly(code: .aborted, message: "\(#function)", echoMetadata: true), - metadata: metadata - ) - - try await control.serverStream(request: request) { response in - switch response.accepted { - case .success: - XCTFail("Expected RPC to be rejected \(pair)") - case .failure(let error): - XCTAssertEqual(error.code, .aborted, "\(pair)") - XCTAssertEqual(error.message, "\(#function)", "\(pair)") - XCTAssertEqual(Array(error.metadata["echo-test-key"]), ["test-value"], "\(pair)") - } - } - } - } - - func testBidiStreamingOK() async throws { - try await self.forEachTransportPair { control, pair in - let metadata: Metadata = ["test-key": "test-value"] - let request = ClientRequest.Stream( - of: ControlInput.self, - metadata: metadata - ) { writer in - try await writer.write(.echoMetadata) - // Send a few messages, each is echo'd back. - try await writer.write(.messages(1, repeating: 42, count: 1024)) - try await writer.write(.messages(1, repeating: 42, count: 1024)) - try await writer.write(.messages(1, repeating: 42, count: 1024)) - // Send the final status. - try await writer.write(.status(code: .ok, message: "", echoMetadata: true)) - } - - try await control.bidiStream(request: request) { response in - switch response.accepted { - case .success(let contents): - XCTAssertEqual(Array(contents.metadata["echo-test-key"]), ["test-value"], "\(pair)") - - var messagesReceived = 0 - for try await part in contents.bodyParts { - switch part { - case .message(let message): - messagesReceived += 1 - XCTAssertEqual(message.payload, Data(repeating: 42, count: 1024)) - case .trailingMetadata(let metadata): - XCTAssertEqual(Array(metadata["echo-test-key"]), ["test-value"], "\(pair)") - } - } - XCTAssertEqual(messagesReceived, 3) - - case .failure(let error): - throw error - } - } - } - } - - func testBidiStreamingEmptyOK() async throws { - try await self.forEachTransportPair { control, pair in - let request = ClientRequest.Stream(of: ControlInput.self) { _ in } - try await control.bidiStream(request: request) { response in - switch response.accepted { - case .success(let contents): - var receivedTrailingMetadata = false - for try await part in contents.bodyParts { - switch part { - case .message: - XCTFail("Unexpected message \(pair)") - case .trailingMetadata: - XCTAssertFalse(receivedTrailingMetadata, "\(pair)") - receivedTrailingMetadata = true - } - } - case .failure(let error): - throw error - } - } - } - } - - func testBidiStreamingNotOK() async throws { - try await self.forEachTransportPair { control, pair in - let metadata: Metadata = ["test-key": "test-value"] - let request = ClientRequest.Stream( - of: ControlInput.self, - metadata: metadata - ) { writer in - try await writer.write(.echoMetadata) - // Send a few messages, each is echo'd back. - try await writer.write(.messages(1, repeating: 42, count: 1024)) - try await writer.write(.messages(1, repeating: 42, count: 1024)) - try await writer.write(.messages(1, repeating: 42, count: 1024)) - // Send the final status. - try await writer.write(.status(code: .aborted, message: "\(#function)", echoMetadata: true)) - } - - try await control.bidiStream(request: request) { response in - switch response.accepted { - case .success(let contents): - XCTAssertEqual(Array(contents.metadata["echo-test-key"]), ["test-value"], "\(pair)") - - var messagesReceived = 0 - do { - for try await part in contents.bodyParts { - switch part { - case .message(let message): - messagesReceived += 1 - XCTAssertEqual(message.payload, Data(repeating: 42, count: 1024)) - case .trailingMetadata: - XCTFail("Trailing metadata should be provided by error") - } - } - XCTFail("Should've thrown error \(pair)") - } catch let error as RPCError { - XCTAssertEqual(error.code, .aborted) - XCTAssertEqual(error.message, "\(#function)") - XCTAssertEqual(Array(error.metadata["echo-test-key"]), ["test-value"], "\(pair)") - } - - XCTAssertEqual(messagesReceived, 3) - - case .failure(let error): - throw error - } - } - } - } - - func testBidiStreamingRejected() async throws { - try await self.forEachTransportPair { control, pair in - let metadata: Metadata = ["test-key": "test-value"] - let request = ClientRequest.Stream( - of: ControlInput.self, - metadata: metadata - ) { writer in - try await writer.write( - .trailersOnly( - code: .aborted, - message: "\(#function)", - echoMetadata: true - ) - ) - } - - try await control.bidiStream(request: request) { response in - switch response.accepted { - case .success: - XCTFail("Expected RPC to fail \(pair)") - case .failure(let error): - XCTAssertEqual(error.code, .aborted) - XCTAssertEqual(error.message, "\(#function)") - XCTAssertEqual(Array(error.metadata["echo-test-key"]), ["test-value"]) - } - } - } - } - - // MARK: - Not Implemented - - func testUnaryNotImplemented() async throws { - try await self.forEachTransportPair(enableControlService: false) { control, pair in - let request = ClientRequest.Single(message: ControlInput()) - try await control.unary(request: request) { response in - XCTAssertThrowsError(ofType: RPCError.self, try response.message) { error in - XCTAssertEqual(error.code, .unimplemented) - } - } - } - } - - func testClientStreamingNotImplemented() async throws { - try await self.forEachTransportPair(enableControlService: false) { control, pair in - let request = ClientRequest.Stream(of: ControlInput.self) { _ in } - try await control.clientStream(request: request) { response in - XCTAssertThrowsError(ofType: RPCError.self, try response.message) { error in - XCTAssertEqual(error.code, .unimplemented) - } - } - } - } - - func testServerStreamingNotImplemented() async throws { - try await self.forEachTransportPair(enableControlService: false) { control, pair in - let request = ClientRequest.Single(message: ControlInput()) - try await control.serverStream(request: request) { response in - XCTAssertThrowsError(ofType: RPCError.self, try response.accepted.get()) { error in - XCTAssertEqual(error.code, .unimplemented) - } - } - } - } - - func testBidiStreamingNotImplemented() async throws { - try await self.forEachTransportPair(enableControlService: false) { control, pair in - let request = ClientRequest.Stream(of: ControlInput.self) { _ in } - try await control.bidiStream(request: request) { response in - XCTAssertThrowsError(ofType: RPCError.self, try response.accepted.get()) { error in - XCTAssertEqual(error.code, .unimplemented) - } - } - } - } - - // MARK: - Compression tests - - private func testUnaryCompression( - client: CompressionAlgorithm, - server: CompressionAlgorithm, - control: ControlClient, - pair: Transport - ) async throws { - let message = ControlInput.with { - $0.echoMetadataInHeaders = true - $0.numberOfMessages = 1 - $0.messageParams = .with { - $0.content = 42 - $0.size = 1024 - } - } - - var options = CallOptions.defaults - options.compression = client - - try await control.unary( - request: ClientRequest.Single(message: message), - options: options - ) { response in - // Check the client algorithm. - switch client { - case .deflate, .gzip: - // "echo-grpc-encoding" is the value of "grpc-encoding" sent from the client to the server. - let encoding = Array(response.metadata["echo-grpc-encoding"]) - XCTAssertEqual(encoding, ["\(client.name)"], "\(pair)") - case .none: - () - default: - XCTFail("Unhandled compression '\(client)'") - } - - // Check the server algorithm. - switch server { - case .deflate, .gzip: - let encoding = Array(response.metadata["grpc-encoding"]) - XCTAssertEqual(encoding, ["\(server.name)"], "\(pair)") - case .none: - () - default: - XCTFail("Unhandled compression '\(client)'") - } - - let message = try response.message - XCTAssertEqual(message.payload, Data(repeating: 42, count: 1024), "\(pair)") - } - } - - private func testClientStreamingCompression( - client: CompressionAlgorithm, - server: CompressionAlgorithm, - control: ControlClient, - pair: Transport - ) async throws { - let request = ClientRequest.Stream(of: ControlInput.self) { writer in - try await writer.write(.echoMetadata) - try await writer.write(.noOp) - try await writer.write(.noOp) - try await writer.write(.messages(1, repeating: 42, count: 1024)) - } - - var options = CallOptions.defaults - options.compression = client - - try await control.clientStream(request: request, options: options) { response in - // Check the client algorithm. - switch client { - case .deflate, .gzip: - // "echo-grpc-encoding" is the value of "grpc-encoding" sent from the client to the server. - let encoding = Array(response.metadata["echo-grpc-encoding"]) - XCTAssertEqual(encoding, ["\(client.name)"], "\(pair)") - case .none: - () - default: - XCTFail("Unhandled compression '\(client)'") - } - - // Check the server algorithm. - switch server { - case .deflate, .gzip: - let encoding = Array(response.metadata["grpc-encoding"]) - XCTAssertEqual(encoding, ["\(server.name)"], "\(pair)") - case .none: - () - default: - XCTFail("Unhandled compression '\(client)'") - } - - let message = try response.message - XCTAssertEqual(message.payload, Data(repeating: 42, count: 1024), "\(pair)") - } - } - - private func testServerStreamingCompression( - client: CompressionAlgorithm, - server: CompressionAlgorithm, - control: ControlClient, - pair: Transport - ) async throws { - let message = ControlInput.with { - $0.echoMetadataInHeaders = true - $0.numberOfMessages = 5 - $0.messageParams = .with { - $0.content = 42 - $0.size = 1024 - } - } - - var options = CallOptions.defaults - options.compression = client - - try await control.serverStream( - request: ClientRequest.Single(message: message), - options: options - ) { response in - // Check the client algorithm. - switch client { - case .deflate, .gzip: - // "echo-grpc-encoding" is the value of "grpc-encoding" sent from the client to the server. - let encoding = Array(response.metadata["echo-grpc-encoding"]) - XCTAssertEqual(encoding, ["\(client.name)"], "\(pair)") - case .none: - () - default: - XCTFail("Unhandled compression '\(client)'") - } - - // Check the server algorithm. - switch server { - case .deflate, .gzip: - let encoding = Array(response.metadata["grpc-encoding"]) - XCTAssertEqual(encoding, ["\(server.name)"], "\(pair)") - case .none: - () - default: - XCTFail("Unhandled compression '\(client)'") - } - - for try await message in response.messages { - XCTAssertEqual(message.payload, Data(repeating: 42, count: 1024), "\(pair)") - } - } - } - - private func testBidiStreamingCompression( - client: CompressionAlgorithm, - server: CompressionAlgorithm, - control: ControlClient, - pair: Transport - ) async throws { - let request = ClientRequest.Stream(of: ControlInput.self) { writer in - try await writer.write(.echoMetadata) - try await writer.write(.messages(1, repeating: 42, count: 1024)) - try await writer.write(.messages(1, repeating: 42, count: 1024)) - try await writer.write(.messages(1, repeating: 42, count: 1024)) - } - - var options = CallOptions.defaults - options.compression = client - - try await control.bidiStream(request: request, options: options) { response in - // Check the client algorithm. - switch client { - case .deflate, .gzip: - // "echo-grpc-encoding" is the value of "grpc-encoding" sent from the client to the server. - let encoding = Array(response.metadata["echo-grpc-encoding"]) - XCTAssertEqual(encoding, ["\(client.name)"], "\(pair)") - case .none: - () - default: - XCTFail("Unhandled compression '\(client)'") - } - - // Check the server algorithm. - switch server { - case .deflate, .gzip: - let encoding = Array(response.metadata["grpc-encoding"]) - XCTAssertEqual(encoding, ["\(server.name)"], "\(pair)") - case .none: - () - default: - XCTFail("Unhandled compression '\(client)'") - } - - for try await message in response.messages { - XCTAssertEqual(message.payload, Data(repeating: 42, count: 1024), "\(pair)") - } - } - } - - func testUnaryDeflateCompression() async throws { - try await self.forEachTransportPair( - clientCompression: .deflate, - clientEnabledCompression: .deflate, - serverCompression: .deflate - ) { control, pair in - try await self.testUnaryCompression( - client: .deflate, - server: .deflate, - control: control, - pair: pair - ) - } - } - - func testUnaryGzipCompression() async throws { - try await self.forEachTransportPair( - clientCompression: .gzip, - clientEnabledCompression: .gzip, - serverCompression: .gzip - ) { control, pair in - try await self.testUnaryCompression( - client: .gzip, - server: .gzip, - control: control, - pair: pair - ) - } - } - - func testClientStreamingDeflateCompression() async throws { - try await self.forEachTransportPair( - clientCompression: .deflate, - clientEnabledCompression: .deflate, - serverCompression: .deflate - ) { control, pair in - try await self.testClientStreamingCompression( - client: .deflate, - server: .deflate, - control: control, - pair: pair - ) - } - } - - func testClientStreamingGzipCompression() async throws { - try await self.forEachTransportPair( - clientCompression: .gzip, - clientEnabledCompression: .gzip, - serverCompression: .gzip - ) { control, pair in - try await self.testClientStreamingCompression( - client: .gzip, - server: .gzip, - control: control, - pair: pair - ) - } - } - - func testServerStreamingDeflateCompression() async throws { - try await self.forEachTransportPair( - clientCompression: .deflate, - clientEnabledCompression: .deflate, - serverCompression: .deflate - ) { control, pair in - try await self.testServerStreamingCompression( - client: .deflate, - server: .deflate, - control: control, - pair: pair - ) - } - } - - func testServerStreamingGzipCompression() async throws { - try await self.forEachTransportPair( - clientCompression: .gzip, - clientEnabledCompression: .gzip, - serverCompression: .gzip - ) { control, pair in - try await self.testServerStreamingCompression( - client: .gzip, - server: .gzip, - control: control, - pair: pair - ) - } - } - - func testBidiStreamingDeflateCompression() async throws { - try await self.forEachTransportPair( - clientCompression: .deflate, - clientEnabledCompression: .deflate, - serverCompression: .deflate - ) { control, pair in - try await self.testBidiStreamingCompression( - client: .deflate, - server: .deflate, - control: control, - pair: pair - ) - } - } - - func testBidiStreamingGzipCompression() async throws { - try await self.forEachTransportPair( - clientCompression: .gzip, - clientEnabledCompression: .gzip, - serverCompression: .gzip - ) { control, pair in - try await self.testBidiStreamingCompression( - client: .gzip, - server: .gzip, - control: control, - pair: pair - ) - } - } - - func testUnaryUnsupportedCompression() async throws { - try await self.forEachTransportPair( - clientEnabledCompression: .all, - serverCompression: .gzip - ) { control, pair in - let message = ControlInput.with { - $0.numberOfMessages = 1 - $0.messageParams = .with { - $0.content = 42 - $0.size = 1024 - } - } - let request = ClientRequest.Single(message: message) - - var options = CallOptions.defaults - options.compression = .deflate - try await control.unary(request: request, options: options) { response in - switch response.accepted { - case .success: - XCTFail("RPC should've been rejected") - case .failure(let error): - let acceptEncoding = Array(error.metadata["grpc-accept-encoding"]) - // "identity" may or may not be included, so only test for values which must be present. - XCTAssertTrue(acceptEncoding.contains("gzip")) - XCTAssertFalse(acceptEncoding.contains("deflate")) - } - } - } - } - - func testClientStreamingUnsupportedCompression() async throws { - try await self.forEachTransportPair( - clientEnabledCompression: .all, - serverCompression: .gzip - ) { control, pair in - let request = ClientRequest.Stream(of: ControlInput.self) { writer in - try await writer.write(.noOp) - } - - var options = CallOptions.defaults - options.compression = .deflate - try await control.clientStream(request: request, options: options) { response in - switch response.accepted { - case .success: - XCTFail("RPC should've been rejected") - case .failure(let error): - let acceptEncoding = Array(error.metadata["grpc-accept-encoding"]) - // "identity" may or may not be included, so only test for values which must be present. - XCTAssertTrue(acceptEncoding.contains("gzip")) - XCTAssertFalse(acceptEncoding.contains("deflate")) - } - } - } - } - - func testServerStreamingUnsupportedCompression() async throws { - try await self.forEachTransportPair( - clientEnabledCompression: .all, - serverCompression: .gzip - ) { control, pair in - let message = ControlInput.with { - $0.numberOfMessages = 1 - $0.messageParams = .with { - $0.content = 42 - $0.size = 1024 - } - } - let request = ClientRequest.Single(message: message) - - var options = CallOptions.defaults - options.compression = .deflate - try await control.serverStream(request: request, options: options) { response in - switch response.accepted { - case .success: - XCTFail("RPC should've been rejected") - case .failure(let error): - let acceptEncoding = Array(error.metadata["grpc-accept-encoding"]) - // "identity" may or may not be included, so only test for values which must be present. - XCTAssertTrue(acceptEncoding.contains("gzip")) - XCTAssertFalse(acceptEncoding.contains("deflate")) - } - } - } - } - - func testBidiStreamingUnsupportedCompression() async throws { - try await self.forEachTransportPair( - clientEnabledCompression: .all, - serverCompression: .gzip - ) { control, pair in - let request = ClientRequest.Stream(of: ControlInput.self) { writer in - try await writer.write(.noOp) - } - - var options = CallOptions.defaults - options.compression = .deflate - try await control.bidiStream(request: request, options: options) { response in - switch response.accepted { - case .success: - XCTFail("RPC should've been rejected") - case .failure(let error): - let acceptEncoding = Array(error.metadata["grpc-accept-encoding"]) - // "identity" may or may not be included, so only test for values which must be present. - XCTAssertTrue(acceptEncoding.contains("gzip")) - XCTAssertFalse(acceptEncoding.contains("deflate")) - } - } - } - } - - func testUnaryTimeoutPropagatedToServer() async throws { - try await self.forEachTransportPair { control, pair in - let message = ControlInput.with { - $0.echoMetadataInHeaders = true - $0.numberOfMessages = 1 - } - - let request = ClientRequest.Single(message: message) - var options = CallOptions.defaults - options.timeout = .seconds(10) - try await control.unary(request: request, options: options) { response in - let timeout = Array(response.metadata["echo-grpc-timeout"]) - XCTAssertEqual(timeout.count, 1) - } - } - } - - func testClientStreamingTimeoutPropagatedToServer() async throws { - try await self.forEachTransportPair { control, pair in - let request = ClientRequest.Stream(of: ControlInput.self) { writer in - let message = ControlInput.with { - $0.echoMetadataInHeaders = true - $0.numberOfMessages = 1 - } - try await writer.write(message) - } - - var options = CallOptions.defaults - options.timeout = .seconds(10) - try await control.clientStream(request: request, options: options) { response in - let timeout = Array(response.metadata["echo-grpc-timeout"]) - XCTAssertEqual(timeout.count, 1) - } - } - } - - func testServerStreamingTimeoutPropagatedToServer() async throws { - try await self.forEachTransportPair { control, pair in - let message = ControlInput.with { - $0.echoMetadataInHeaders = true - $0.numberOfMessages = 1 - } - - let request = ClientRequest.Single(message: message) - var options = CallOptions.defaults - options.timeout = .seconds(10) - try await control.serverStream(request: request, options: options) { response in - let timeout = Array(response.metadata["echo-grpc-timeout"]) - XCTAssertEqual(timeout.count, 1) - } - } - } - - func testBidiStreamingTimeoutPropagatedToServer() async throws { - try await self.forEachTransportPair { control, pair in - let request = ClientRequest.Stream(of: ControlInput.self) { writer in - try await writer.write(.echoMetadata) - } - - var options = CallOptions.defaults - options.timeout = .seconds(10) - try await control.bidiStream(request: request, options: options) { response in - let timeout = Array(response.metadata["echo-grpc-timeout"]) - XCTAssertEqual(timeout.count, 1) - } - } - } - - private static let httpToStatusCodePairs: [(Int, RPCError.Code)] = [ - // See https://github.com/grpc/grpc/blob/master/doc/http-grpc-status-mapping.md - (400, .internalError), - (401, .unauthenticated), - (403, .permissionDenied), - (404, .unimplemented), - (418, .unknown), - (429, .unavailable), - (502, .unavailable), - (503, .unavailable), - (504, .unavailable), - (504, .unavailable), - ] - - func testUnaryAgainstNonGRPCServer() async throws { - try await self.forEachClientAndHTTPStatusCodeServer { control, kind in - for (httpCode, expectedStatus) in Self.httpToStatusCodePairs { - // Tell the server what to respond with. - let metadata: Metadata = ["response-status": "\(httpCode)"] - - try await control.unary( - request: ClientRequest.Single(message: .noOp, metadata: metadata) - ) { response in - switch response.accepted { - case .success: - XCTFail("RPC should have failed with '\(expectedStatus)'") - case .failure(let error): - XCTAssertEqual(error.code, expectedStatus) - } - } - } - } - } - - func testClientStreamingAgainstNonGRPCServer() async throws { - try await self.forEachClientAndHTTPStatusCodeServer { control, kind in - for (httpCode, expectedStatus) in Self.httpToStatusCodePairs { - // Tell the server what to respond with. - let request = ClientRequest.Stream( - of: ControlInput.self, - metadata: ["response-status": "\(httpCode)"] - ) { _ in - } - - try await control.clientStream(request: request) { response in - switch response.accepted { - case .success: - XCTFail("RPC should have failed with '\(expectedStatus)'") - case .failure(let error): - XCTAssertEqual(error.code, expectedStatus) - } - } - } - } - } - - func testServerStreamingAgainstNonGRPCServer() async throws { - try await self.forEachClientAndHTTPStatusCodeServer { control, kind in - for (httpCode, expectedStatus) in Self.httpToStatusCodePairs { - // Tell the server what to respond with. - let metadata: Metadata = ["response-status": "\(httpCode)"] - - try await control.serverStream( - request: ClientRequest.Single(message: .noOp, metadata: metadata) - ) { response in - switch response.accepted { - case .success: - XCTFail("RPC should have failed with '\(expectedStatus)'") - case .failure(let error): - XCTAssertEqual(error.code, expectedStatus) - } - } - } - } - } - - func testBidiStreamingAgainstNonGRPCServer() async throws { - try await self.forEachClientAndHTTPStatusCodeServer { control, kind in - for (httpCode, expectedStatus) in Self.httpToStatusCodePairs { - // Tell the server what to respond with. - let request = ClientRequest.Stream( - of: ControlInput.self, - metadata: ["response-status": "\(httpCode)"] - ) { _ in - } - - try await control.bidiStream(request: request) { response in - switch response.accepted { - case .success: - XCTFail("RPC should have failed with '\(expectedStatus)'") - case .failure(let error): - XCTAssertEqual(error.code, expectedStatus) - } - } - } - } - } - - func testUnaryScheme() async throws { - try await self.forEachTransportPair { control, pair in - let input = ControlInput.with { - $0.echoMetadataInHeaders = true - $0.numberOfMessages = 1 - } - let request = ClientRequest.Single(message: input) - try await control.unary(request: request) { response in - XCTAssertEqual(Array(response.metadata["echo-scheme"]), ["http"]) - } - } - } - - func testServerStreamingScheme() async throws { - try await self.forEachTransportPair { control, pair in - let input = ControlInput.with { - $0.echoMetadataInHeaders = true - $0.numberOfMessages = 1 - } - let request = ClientRequest.Single(message: input) - try await control.serverStream(request: request) { response in - XCTAssertEqual(Array(response.metadata["echo-scheme"]), ["http"]) - } - } - } - - func testClientStreamingScheme() async throws { - try await self.forEachTransportPair { control, pair in - let request = ClientRequest.Stream(of: ControlInput.self) { writer in - let input = ControlInput.with { - $0.echoMetadataInHeaders = true - $0.numberOfMessages = 1 - } - try await writer.write(input) - } - try await control.clientStream(request: request) { response in - XCTAssertEqual(Array(response.metadata["echo-scheme"]), ["http"]) - } - } - } - - func testBidiStreamingScheme() async throws { - try await self.forEachTransportPair { control, pair in - let request = ClientRequest.Stream(of: ControlInput.self) { writer in - let input = ControlInput.with { - $0.echoMetadataInHeaders = true - $0.numberOfMessages = 1 - } - try await writer.write(input) - } - try await control.bidiStream(request: request) { response in - XCTAssertEqual(Array(response.metadata["echo-scheme"]), ["http"]) - } - } - } -} - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -extension [HTTP2TransportTests.Transport] { - static let supported = [ - HTTP2TransportTests.Transport(server: .posix, client: .posix), - HTTP2TransportTests.Transport(server: .niots, client: .niots), - HTTP2TransportTests.Transport(server: .niots, client: .posix), - HTTP2TransportTests.Transport(server: .posix, client: .niots), - ] -} - -extension ControlInput { - fileprivate static let echoMetadata = Self.with { - $0.echoMetadataInHeaders = true - } - - fileprivate static let noOp = Self() - - fileprivate static func messages( - _ numberOfMessages: Int, - repeating: UInt8, - count: Int - ) -> Self { - return Self.with { - $0.numberOfMessages = Int32(numberOfMessages) - $0.messageParams = .with { - $0.content = UInt32(repeating) - $0.size = Int32(count) - } - } - } - - fileprivate static func status( - code: Status.Code, - message: String, - echoMetadata: Bool - ) -> Self { - return Self.with { - $0.echoMetadataInTrailers = echoMetadata - $0.status = .with { - $0.code = StatusCode(rawValue: code.rawValue)! - $0.message = message - } - } - } - - fileprivate static func trailersOnly( - code: Status.Code, - message: String, - echoMetadata: Bool - ) -> Self { - return Self.with { - $0.echoMetadataInTrailers = echoMetadata - $0.isTrailersOnly = true - $0.status = .with { - $0.code = StatusCode(rawValue: code.rawValue)! - $0.message = message - } - } - } -} - -extension CompressionAlgorithm { - var name: String { - switch self { - case .deflate: - return "deflate" - case .gzip: - return "gzip" - case .none: - return "identity" - default: - return "" - } - } -} diff --git a/Tests/GRPCHTTP2TransportTests/Test Utilities/HTTP2StatusCodeServer.swift b/Tests/GRPCHTTP2TransportTests/Test Utilities/HTTP2StatusCodeServer.swift deleted file mode 100644 index d4f6e00ee..000000000 --- a/Tests/GRPCHTTP2TransportTests/Test Utilities/HTTP2StatusCodeServer.swift +++ /dev/null @@ -1,113 +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 GRPCHTTP2Core -import NIOCore -import NIOHPACK -import NIOHTTP2 -import NIOPosix - -/// An HTTP/2 test server which only responds to request headers by sending response headers and -/// then closing. Each stream will be closed with the ":status" set to the value of the -/// "response-status" header field in the request headers. -@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) -final class HTTP2StatusCodeServer: Sendable { - private let address: EventLoopPromise - private let eventLoopGroup: MultiThreadedEventLoopGroup - - var listeningAddress: GRPCHTTP2Core.SocketAddress.IPv4 { - get async throws { - try await self.address.futureResult.get() - } - } - - init() { - self.eventLoopGroup = .singleton - self.address = self.eventLoopGroup.next().makePromise() - } - - func run() async throws { - do { - let channel = try await ServerBootstrap(group: .singletonMultiThreadedEventLoopGroup) - .bind(host: "127.0.0.1", port: 0) { channel in - channel.eventLoop.makeCompletedFuture { - let sync = channel.pipeline.syncOperations - let multiplexer = try sync.configureAsyncHTTP2Pipeline(mode: .server) { stream in - stream.eventLoop.makeCompletedFuture { - try NIOAsyncChannel( - wrappingChannelSynchronously: stream - ) - } - } - - let wrapped = try NIOAsyncChannel( - wrappingChannelSynchronously: channel - ) - - return (wrapped, multiplexer) - } - } - - let port = channel.channel.localAddress!.port! - self.address.succeed(.init(host: "127.0.0.1", port: port)) - - try await channel.executeThenClose { inbound in - try await withThrowingTaskGroup(of: Void.self) { acceptedGroup in - for try await (accepted, mux) in inbound { - acceptedGroup.addTask { - try await withThrowingTaskGroup(of: Void.self) { connectionGroup in - // Run the connection. - connectionGroup.addTask { - try await accepted.executeThenClose { inbound, outbound in - for try await _ in inbound {} - } - } - - // Consume the streams. - for try await stream in mux.inbound { - connectionGroup.addTask { - try await stream.executeThenClose { inbound, outbound in - do { - for try await frame in inbound { - switch frame { - case .headers(let requestHeaders): - if let status = requestHeaders.headers.first(name: "response-status") { - let headers: HPACKHeaders = [":status": "\(status)"] - try await outbound.write( - .headers(.init(headers: headers, endStream: true)) - ) - } - - default: - () // Ignore the others - } - } - } catch { - // Ignore errors - } - } - } - } - } - } - } - } - } - } catch { - self.address.fail(error) - } - } -} diff --git a/Tests/GRPCHTTP2TransportTests/Test Utilities/XCTest+Utilities.swift b/Tests/GRPCHTTP2TransportTests/Test Utilities/XCTest+Utilities.swift deleted file mode 100644 index 3848388bc..000000000 --- a/Tests/GRPCHTTP2TransportTests/Test Utilities/XCTest+Utilities.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. - */ - -import XCTest - -func XCTAssertThrowsError( - ofType: E.Type, - _ expression: @autoclosure () throws -> T, - _ errorHandler: (E) -> Void -) { - XCTAssertThrowsError(try expression()) { error in - guard let error = error as? E else { - return XCTFail("Error had unexpected type '\(type(of: error))'") - } - errorHandler(error) - } -} diff --git a/Tests/GRPCHTTP2TransportTests/XCTestCase+Vsock.swift b/Tests/GRPCHTTP2TransportTests/XCTestCase+Vsock.swift deleted file mode 100644 index cb613fb63..000000000 --- a/Tests/GRPCHTTP2TransportTests/XCTestCase+Vsock.swift +++ /dev/null @@ -1,34 +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 NIOPosix -import XCTest - -extension XCTestCase { - func vsockAvailable() -> Bool { - let fd: CInt - #if os(Linux) - fd = socket(AF_VSOCK, CInt(SOCK_STREAM.rawValue), 0) - #elseif canImport(Darwin) - fd = socket(AF_VSOCK, SOCK_STREAM, 0) - #else - fd = -1 - #endif - if fd == -1 { return false } - precondition(close(fd) == 0) - return true - } -} diff --git a/Tests/GRPCInProcessTransportTests/InProcessClientTransportTests.swift b/Tests/GRPCInProcessTransportTests/InProcessClientTransportTests.swift deleted file mode 100644 index d33b2774d..000000000 --- a/Tests/GRPCInProcessTransportTests/InProcessClientTransportTests.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. - */ - -import GRPCCore -import GRPCInProcessTransport -import XCTest - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -final class InProcessClientTransportTests: XCTestCase { - struct FailTest: Error {} - - func testConnectWhenConnected() async { - let client = makeClient() - - await withThrowingTaskGroup(of: Void.self) { group in - group.addTask { - try await client.connect() - } - - group.addTask { - try await client.connect() - } - - await XCTAssertThrowsErrorAsync(ofType: RPCError.self) { - try await group.next() - } errorHandler: { error in - XCTAssertEqual(error.code, .failedPrecondition) - } - group.cancelAll() - } - } - - func testConnectWhenClosed() async { - let client = makeClient() - - client.beginGracefulShutdown() - - await XCTAssertThrowsErrorAsync(ofType: RPCError.self) { - try await client.connect() - } errorHandler: { error in - XCTAssertEqual(error.code, .failedPrecondition) - } - } - - func testConnectWhenClosedAfterCancellation() async throws { - let client = makeClient() - - try await withThrowingTaskGroup(of: Void.self) { group in - group.addTask { - try await client.connect() - } - group.addTask { - try await Task.sleep(for: .milliseconds(100)) - } - - try await group.next() - group.cancelAll() - - await XCTAssertThrowsErrorAsync(ofType: RPCError.self) { - try await client.connect() - } errorHandler: { error in - XCTAssertEqual(error.code, .failedPrecondition) - } - } - } - - func testCloseWhenUnconnected() { - let client = makeClient() - - XCTAssertNoThrow(client.beginGracefulShutdown()) - } - - func testCloseWhenClosed() { - let client = makeClient() - client.beginGracefulShutdown() - - XCTAssertNoThrow(client.beginGracefulShutdown()) - } - - func testConnectSuccessfullyAndThenClose() async throws { - let client = makeClient() - - try await withThrowingTaskGroup(of: Void.self) { group in - group.addTask { - try await client.connect() - } - group.addTask { - try await Task.sleep(for: .milliseconds(100)) - } - - try await group.next() - client.beginGracefulShutdown() - } - } - - func testOpenStreamWhenUnconnected() async throws { - let client = makeClient() - - try await withThrowingTaskGroup(of: Void.self) { group in - group.addTask { - try await client.withStream( - descriptor: .init(service: "test", method: "test"), - options: .defaults - ) { _ in - // Once the pending stream is opened, close the client to new connections, - // so that, once this closure is executed and this stream is closed, - // the client will return from `connect()`. - client.beginGracefulShutdown() - } - } - - group.addTask { - // Add a sleep to make sure connection happens after `withStream` has been called, - // to test pending streams are handled correctly. - try await Task.sleep(for: .milliseconds(100)) - try await client.connect() - } - - try await group.waitForAll() - } - } - - func testOpenStreamWhenClosed() async { - let client = makeClient() - - client.beginGracefulShutdown() - - await XCTAssertThrowsErrorAsync(ofType: RPCError.self) { - try await client.withStream( - descriptor: .init(service: "test", method: "test"), - options: .defaults - ) { _ in } - } errorHandler: { error in - XCTAssertEqual(error.code, .failedPrecondition) - } - } - - func testOpenStreamSuccessfullyAndThenClose() async throws { - let server = InProcessServerTransport() - let client = makeClient(server: server) - - try await withThrowingTaskGroup(of: Void.self) { group in - group.addTask { - try await client.connect() - } - - group.addTask { - try await client.withStream( - descriptor: .init(service: "test", method: "test"), - options: .defaults - ) { stream in - try await stream.outbound.write(.message([1])) - await stream.outbound.finish() - let receivedMessages = try await stream.inbound.reduce(into: []) { $0.append($1) } - - XCTAssertEqual(receivedMessages, [.message([42])]) - } - } - - group.addTask { - try await server.listen { stream, context in - let receivedMessages = try? await stream.inbound.reduce(into: []) { $0.append($1) } - try? await stream.outbound.write(RPCResponsePart.message([42])) - await stream.outbound.finish() - - XCTAssertEqual(receivedMessages, [.message([1])]) - } - } - - group.addTask { - try await Task.sleep(for: .milliseconds(100)) - client.beginGracefulShutdown() - } - - try await group.next() - group.cancelAll() - } - } - - func testExecutionConfiguration() { - let policy = HedgingPolicy( - maxAttempts: 10, - hedgingDelay: .seconds(1), - nonFatalStatusCodes: [] - ) - - var serviceConfig = ServiceConfig( - methodConfig: [ - MethodConfig( - names: [ - MethodConfig.Name(service: "", method: "") - ], - executionPolicy: .hedge(policy) - ) - ] - ) - - var client = InProcessClientTransport( - server: InProcessServerTransport(), - serviceConfig: serviceConfig - ) - - let firstDescriptor = MethodDescriptor(service: "test", method: "first") - XCTAssertEqual( - client.config(forMethod: firstDescriptor), - serviceConfig.methodConfig.first - ) - - let retryPolicy = RetryPolicy( - maxAttempts: 10, - initialBackoff: .seconds(1), - maxBackoff: .seconds(1), - backoffMultiplier: 1.0, - retryableStatusCodes: [.unavailable] - ) - - let overrideConfiguration = MethodConfig( - names: [MethodConfig.Name(service: "test", method: "second")], - executionPolicy: .retry(retryPolicy) - ) - serviceConfig.methodConfig.append(overrideConfiguration) - client = InProcessClientTransport( - server: InProcessServerTransport(), - serviceConfig: serviceConfig - ) - - let secondDescriptor = MethodDescriptor(service: "test", method: "second") - XCTAssertEqual( - client.config(forMethod: firstDescriptor), - serviceConfig.methodConfig.first - ) - XCTAssertEqual( - client.config(forMethod: secondDescriptor), - serviceConfig.methodConfig.last - ) - } - - func testOpenMultipleStreamsThenClose() async throws { - let server = InProcessServerTransport() - let client = makeClient(server: server) - - try await withThrowingTaskGroup(of: Void.self) { group in - group.addTask { - try await client.connect() - } - - group.addTask { - try await client.withStream( - descriptor: .init(service: "test", method: "test"), - options: .defaults - ) { stream in - try await Task.sleep(for: .milliseconds(100)) - } - } - - group.addTask { - try await client.withStream( - descriptor: .init(service: "test", method: "test"), - options: .defaults - ) { stream in - try await Task.sleep(for: .milliseconds(100)) - } - } - - group.addTask { - try await Task.sleep(for: .milliseconds(50)) - client.beginGracefulShutdown() - } - - try await group.next() - } - } - - func makeClient( - server: InProcessServerTransport = InProcessServerTransport() - ) -> InProcessClientTransport { - let defaultPolicy = RetryPolicy( - maxAttempts: 10, - initialBackoff: .seconds(1), - maxBackoff: .seconds(1), - backoffMultiplier: 1.0, - retryableStatusCodes: [.unavailable] - ) - - let serviceConfig = ServiceConfig( - methodConfig: [ - MethodConfig( - names: [MethodConfig.Name(service: "", method: "")], - executionPolicy: .retry(defaultPolicy) - ) - ] - ) - - return InProcessClientTransport( - server: server, - serviceConfig: serviceConfig - ) - } -} diff --git a/Tests/GRPCInProcessTransportTests/InProcessServerTransportTests.swift b/Tests/GRPCInProcessTransportTests/InProcessServerTransportTests.swift deleted file mode 100644 index 7cd8fed20..000000000 --- a/Tests/GRPCInProcessTransportTests/InProcessServerTransportTests.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. - */ - -import XCTest - -@testable import GRPCCore -@testable import GRPCInProcessTransport - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -final class InProcessServerTransportTests: XCTestCase { - func testStartListening() async throws { - let transport = InProcessServerTransport() - - let outbound = GRPCAsyncThrowingStream.makeStream(of: RPCResponsePart.self) - let stream = RPCStream< - RPCAsyncSequence, - RPCWriter.Closable - >( - descriptor: .init(service: "testService", method: "testMethod"), - inbound: RPCAsyncSequence( - wrapping: AsyncThrowingStream { - $0.yield(.message([42])) - $0.finish() - } - ), - outbound: RPCWriter.Closable(wrapping: outbound.continuation) - ) - - try await withThrowingTaskGroup(of: Void.self) { group in - group.addTask { - try await transport.listen { stream, context in - XCTAssertEqual(context.descriptor, stream.descriptor) - let partValue = try? await stream.inbound.reduce(into: []) { $0.append($1) } - XCTAssertEqual(partValue, [.message([42])]) - transport.beginGracefulShutdown() - } - } - - try transport.acceptStream(stream) - } - } - - func testStopListening() async throws { - let transport = InProcessServerTransport() - - let firstStreamOutbound = GRPCAsyncThrowingStream.makeStream(of: RPCResponsePart.self) - let firstStream = RPCStream< - RPCAsyncSequence, RPCWriter.Closable - >( - descriptor: .init(service: "testService1", method: "testMethod1"), - inbound: RPCAsyncSequence( - wrapping: AsyncThrowingStream { - $0.yield(.message([42])) - $0.finish() - } - ), - outbound: RPCWriter.Closable(wrapping: firstStreamOutbound.continuation) - ) - - try transport.acceptStream(firstStream) - - try await transport.listen { stream, context in - let firstStreamMessages = try? await stream.inbound.reduce(into: []) { - $0.append($1) - } - XCTAssertEqual(firstStreamMessages, [.message([42])]) - - transport.beginGracefulShutdown() - - let secondStreamOutbound = GRPCAsyncThrowingStream.makeStream(of: RPCResponsePart.self) - let secondStream = RPCStream< - RPCAsyncSequence, RPCWriter.Closable - >( - descriptor: .init(service: "testService1", method: "testMethod1"), - inbound: RPCAsyncSequence( - wrapping: AsyncThrowingStream { - $0.yield(.message([42])) - $0.finish() - } - ), - outbound: RPCWriter.Closable(wrapping: secondStreamOutbound.continuation) - ) - - XCTAssertThrowsError(ofType: RPCError.self) { - try transport.acceptStream(secondStream) - } errorHandler: { error in - XCTAssertEqual(error.code, .failedPrecondition) - XCTAssertEqual(error.message, "The server transport is closed.") - } - } - } -} diff --git a/Tests/GRPCInProcessTransportTests/Test Utilities/XCTest+Utilities.swift b/Tests/GRPCInProcessTransportTests/Test Utilities/XCTest+Utilities.swift deleted file mode 100644 index 67d381073..000000000 --- a/Tests/GRPCInProcessTransportTests/Test Utilities/XCTest+Utilities.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. - */ -import XCTest - -func XCTAssertThrowsError( - ofType: E.Type, - _ expression: () throws -> T, - errorHandler: (E) -> Void -) { - XCTAssertThrowsError(try expression()) { error in - guard let error = error as? E else { - return XCTFail("Error had unexpected type '\(type(of: error))'") - } - errorHandler(error) - } -} - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -func XCTAssertThrowsErrorAsync( - ofType: E.Type = E.self, - _ expression: () async throws -> T, - errorHandler: (E) -> Void -) async { - do { - _ = try await expression() - XCTFail("Expression didn't throw") - } catch let error as E { - errorHandler(error) - } catch { - XCTFail("Error had unexpected type '\(type(of: error))'") - } -} diff --git a/Tests/GRPCInterceptorsTests/TracingInterceptorTests.swift b/Tests/GRPCInterceptorsTests/TracingInterceptorTests.swift deleted file mode 100644 index 5535c2d6c..000000000 --- a/Tests/GRPCInterceptorsTests/TracingInterceptorTests.swift +++ /dev/null @@ -1,334 +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 Tracing -import XCTest - -@testable import GRPCInterceptors - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -final class TracingInterceptorTests: XCTestCase { - override class func setUp() { - InstrumentationSystem.bootstrap(TestTracer()) - } - - func testClientInterceptor() async throws { - var serviceContext = ServiceContext.topLevel - let traceIDString = UUID().uuidString - let interceptor = ClientTracingInterceptor(emitEventOnEachWrite: false) - let (stream, continuation) = AsyncStream.makeStream() - serviceContext.traceID = traceIDString - - try await ServiceContext.withValue(serviceContext) { - let methodDescriptor = MethodDescriptor( - service: "TracingInterceptorTests", - method: "testClientInterceptor" - ) - let response = try await interceptor.intercept( - request: .init(producer: { writer in - try await writer.write(contentsOf: ["request1"]) - try await writer.write(contentsOf: ["request2"]) - }), - context: .init(descriptor: methodDescriptor) - ) { stream, _ in - // Assert the metadata contains the injected context key-value. - XCTAssertEqual(stream.metadata, ["trace-id": "\(traceIDString)"]) - - // Write into the response stream to make sure the `producer` closure's called. - let writer = RPCWriter(wrapping: TestWriter(streamContinuation: continuation)) - try await stream.producer(writer) - continuation.finish() - - return .init( - metadata: [], - bodyParts: RPCAsyncSequence( - wrapping: AsyncThrowingStream { - $0.yield(.message(["response"])) - $0.finish() - } - ) - ) - } - - var streamIterator = stream.makeAsyncIterator() - var element = await streamIterator.next() - XCTAssertEqual(element, "request1") - element = await streamIterator.next() - XCTAssertEqual(element, "request2") - element = await streamIterator.next() - XCTAssertNil(element) - - var messages = response.messages.makeAsyncIterator() - var message = try await messages.next() - XCTAssertEqual(message, ["response"]) - message = try await messages.next() - XCTAssertNil(message) - - let tracer = InstrumentationSystem.tracer as! TestTracer - XCTAssertEqual( - tracer.getEventsForTestSpan(ofOperationName: methodDescriptor.fullyQualifiedMethod).map { - $0.name - }, - [ - "Request started", - "Received response end", - ] - ) - } - } - - func testClientInterceptorAllEventsRecorded() async throws { - let methodDescriptor = MethodDescriptor( - service: "TracingInterceptorTests", - method: "testClientInterceptorAllEventsRecorded" - ) - var serviceContext = ServiceContext.topLevel - let traceIDString = UUID().uuidString - let interceptor = ClientTracingInterceptor(emitEventOnEachWrite: true) - let (stream, continuation) = AsyncStream.makeStream() - serviceContext.traceID = traceIDString - - try await ServiceContext.withValue(serviceContext) { - let response = try await interceptor.intercept( - request: .init(producer: { writer in - try await writer.write(contentsOf: ["request1"]) - try await writer.write(contentsOf: ["request2"]) - }), - context: .init(descriptor: methodDescriptor) - ) { stream, _ in - // Assert the metadata contains the injected context key-value. - XCTAssertEqual(stream.metadata, ["trace-id": "\(traceIDString)"]) - - // Write into the response stream to make sure the `producer` closure's called. - let writer = RPCWriter(wrapping: TestWriter(streamContinuation: continuation)) - try await stream.producer(writer) - continuation.finish() - - return .init( - metadata: [], - bodyParts: RPCAsyncSequence( - wrapping: AsyncThrowingStream { - $0.yield(.message(["response"])) - $0.finish() - } - ) - ) - } - - var streamIterator = stream.makeAsyncIterator() - var element = await streamIterator.next() - XCTAssertEqual(element, "request1") - element = await streamIterator.next() - XCTAssertEqual(element, "request2") - element = await streamIterator.next() - XCTAssertNil(element) - - var messages = response.messages.makeAsyncIterator() - var message = try await messages.next() - XCTAssertEqual(message, ["response"]) - message = try await messages.next() - XCTAssertNil(message) - - let tracer = InstrumentationSystem.tracer as! TestTracer - XCTAssertEqual( - tracer.getEventsForTestSpan(ofOperationName: methodDescriptor.fullyQualifiedMethod).map { - $0.name - }, - [ - "Request started", - // Recorded when `request1` is sent - "Sending request part", - "Sent request part", - // Recorded when `request2` is sent - "Sending request part", - "Sent request part", - // Recorded after all request parts have been sent - "Request end", - // Recorded when receiving response part - "Received response part", - // Recorded at end of response - "Received response end", - ] - ) - } - } - - func testServerInterceptorErrorResponse() async throws { - let methodDescriptor = MethodDescriptor( - service: "TracingInterceptorTests", - method: "testServerInterceptorErrorResponse" - ) - let interceptor = ServerTracingInterceptor(emitEventOnEachWrite: false) - let single = ServerRequest.Single(metadata: ["trace-id": "some-trace-id"], message: [UInt8]()) - let response = try await interceptor.intercept( - request: .init(single: single), - context: .init(descriptor: methodDescriptor) - ) { _, _ in - ServerResponse.Stream(error: .init(code: .unknown, message: "Test error")) - } - XCTAssertThrowsError(try response.accepted.get()) - - let tracer = InstrumentationSystem.tracer as! TestTracer - XCTAssertEqual( - tracer.getEventsForTestSpan(ofOperationName: methodDescriptor.fullyQualifiedMethod).map { - $0.name - }, - [ - "Received request start", - "Received request end", - "Sent error response", - ] - ) - } - - func testServerInterceptor() async throws { - let methodDescriptor = MethodDescriptor( - service: "TracingInterceptorTests", - method: "testServerInterceptor" - ) - let (stream, continuation) = AsyncStream.makeStream() - let interceptor = ServerTracingInterceptor(emitEventOnEachWrite: false) - let single = ServerRequest.Single(metadata: ["trace-id": "some-trace-id"], message: [UInt8]()) - let response = try await interceptor.intercept( - request: .init(single: single), - context: .init(descriptor: methodDescriptor) - ) { _, _ in - { [serviceContext = ServiceContext.current] in - return ServerResponse.Stream( - accepted: .success( - .init( - metadata: [], - producer: { writer in - guard let serviceContext else { - XCTFail("There should be a service context present.") - return ["Result": "Test failed"] - } - - let traceID = serviceContext.traceID - XCTAssertEqual("some-trace-id", traceID) - - try await writer.write("response1") - try await writer.write("response2") - - return ["Result": "Trailing metadata"] - } - ) - ) - ) - }() - } - - let responseContents = try response.accepted.get() - let trailingMetadata = try await responseContents.producer( - RPCWriter(wrapping: TestWriter(streamContinuation: continuation)) - ) - continuation.finish() - XCTAssertEqual(trailingMetadata, ["Result": "Trailing metadata"]) - - var streamIterator = stream.makeAsyncIterator() - var element = await streamIterator.next() - XCTAssertEqual(element, "response1") - element = await streamIterator.next() - XCTAssertEqual(element, "response2") - element = await streamIterator.next() - XCTAssertNil(element) - - let tracer = InstrumentationSystem.tracer as! TestTracer - XCTAssertEqual( - tracer.getEventsForTestSpan(ofOperationName: methodDescriptor.fullyQualifiedMethod).map { - $0.name - }, - [ - "Received request start", - "Received request end", - "Sent response end", - ] - ) - } - - func testServerInterceptorAllEventsRecorded() async throws { - let methodDescriptor = MethodDescriptor( - service: "TracingInterceptorTests", - method: "testServerInterceptorAllEventsRecorded" - ) - let (stream, continuation) = AsyncStream.makeStream() - let interceptor = ServerTracingInterceptor(emitEventOnEachWrite: true) - let single = ServerRequest.Single(metadata: ["trace-id": "some-trace-id"], message: [UInt8]()) - let response = try await interceptor.intercept( - request: .init(single: single), - context: .init(descriptor: methodDescriptor) - ) { _, _ in - { [serviceContext = ServiceContext.current] in - return ServerResponse.Stream( - accepted: .success( - .init( - metadata: [], - producer: { writer in - guard let serviceContext else { - XCTFail("There should be a service context present.") - return ["Result": "Test failed"] - } - - let traceID = serviceContext.traceID - XCTAssertEqual("some-trace-id", traceID) - - try await writer.write("response1") - try await writer.write("response2") - - return ["Result": "Trailing metadata"] - } - ) - ) - ) - }() - } - - let responseContents = try response.accepted.get() - let trailingMetadata = try await responseContents.producer( - RPCWriter(wrapping: TestWriter(streamContinuation: continuation)) - ) - continuation.finish() - XCTAssertEqual(trailingMetadata, ["Result": "Trailing metadata"]) - - var streamIterator = stream.makeAsyncIterator() - var element = await streamIterator.next() - XCTAssertEqual(element, "response1") - element = await streamIterator.next() - XCTAssertEqual(element, "response2") - element = await streamIterator.next() - XCTAssertNil(element) - - let tracer = InstrumentationSystem.tracer as! TestTracer - XCTAssertEqual( - tracer.getEventsForTestSpan(ofOperationName: methodDescriptor.fullyQualifiedMethod).map { - $0.name - }, - [ - "Received request start", - "Received request end", - // Recorded when `response1` is sent - "Sending response part", - "Sent response part", - // Recorded when `response2` is sent - "Sending response part", - "Sent response part", - // Recorded when we're done sending response - "Sent response end", - ] - ) - } -} diff --git a/Tests/GRPCInterceptorsTests/TracingTestsUtilities.swift b/Tests/GRPCInterceptorsTests/TracingTestsUtilities.swift deleted file mode 100644 index 218541fa2..000000000 --- a/Tests/GRPCInterceptorsTests/TracingTestsUtilities.swift +++ /dev/null @@ -1,195 +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 NIOConcurrencyHelpers -import Tracing - -final class TestTracer: Tracer { - typealias Span = TestSpan - - private let testSpans: NIOLockedValueBox<[String: TestSpan]> = .init([:]) - - func getEventsForTestSpan(ofOperationName operationName: String) -> [SpanEvent] { - let span = self.testSpans.withLockedValue({ $0[operationName] }) - return span?.events ?? [] - } - - func extract( - _ carrier: Carrier, - into context: inout ServiceContextModule.ServiceContext, - using extractor: Extract - ) where Carrier == Extract.Carrier, Extract: Instrumentation.Extractor { - let traceID = extractor.extract(key: TraceID.keyName, from: carrier) - context[TraceID.self] = traceID - } - - func inject( - _ context: ServiceContextModule.ServiceContext, - into carrier: inout Carrier, - using injector: Inject - ) where Carrier == Inject.Carrier, Inject: Instrumentation.Injector { - if let traceID = context.traceID { - injector.inject(traceID, forKey: TraceID.keyName, into: &carrier) - } - } - - func forceFlush() { - // no-op - } - - func startSpan( - _ operationName: String, - context: @autoclosure () -> ServiceContext, - ofKind kind: SpanKind, - at instant: @autoclosure () -> Instant, - function: String, - file fileID: String, - line: UInt - ) -> TestSpan where Instant: TracerInstant { - return self.testSpans.withLockedValue { testSpans in - let span = TestSpan(context: context(), operationName: operationName) - testSpans[operationName] = span - return span - } - } -} - -struct TestSpan: Span, Sendable { - private struct State { - var context: ServiceContextModule.ServiceContext - var operationName: String - var attributes: Tracing.SpanAttributes - var status: Tracing.SpanStatus? - var events: [Tracing.SpanEvent] = [] - } - - private let state: NIOLockedValueBox - let isRecording: Bool - - var context: ServiceContextModule.ServiceContext { - self.state.withLockedValue { $0.context } - } - - var operationName: String { - get { self.state.withLockedValue { $0.operationName } } - nonmutating set { self.state.withLockedValue { $0.operationName = newValue } } - } - - var attributes: Tracing.SpanAttributes { - get { self.state.withLockedValue { $0.attributes } } - nonmutating set { self.state.withLockedValue { $0.attributes = newValue } } - } - - var events: [Tracing.SpanEvent] { - self.state.withLockedValue { $0.events } - } - - init( - context: ServiceContextModule.ServiceContext, - operationName: String, - attributes: Tracing.SpanAttributes = [:], - isRecording: Bool = true - ) { - let state = State(context: context, operationName: operationName, attributes: attributes) - self.state = NIOLockedValueBox(state) - self.isRecording = isRecording - } - - func setStatus(_ status: Tracing.SpanStatus) { - self.state.withLockedValue { $0.status = status } - } - - func addEvent(_ event: Tracing.SpanEvent) { - self.state.withLockedValue { $0.events.append(event) } - } - - func recordError( - _ error: any Error, - attributes: Tracing.SpanAttributes, - at instant: @autoclosure () -> Instant - ) where Instant: Tracing.TracerInstant { - self.setStatus( - .init( - code: .error, - message: "Error: \(error), attributes: \(attributes), at instant: \(instant())" - ) - ) - } - - func addLink(_ link: Tracing.SpanLink) { - self.state.withLockedValue { - $0.context.spanLinks?.append(link) - } - } - - func end(at instant: @autoclosure () -> Instant) where Instant: Tracing.TracerInstant { - self.setStatus(.init(code: .ok, message: "Ended at instant: \(instant())")) - } -} - -enum TraceID: ServiceContextModule.ServiceContextKey { - typealias Value = String - - static let keyName = "trace-id" -} - -enum ServiceContextSpanLinksKey: ServiceContextModule.ServiceContextKey { - typealias Value = [SpanLink] - - static let keyName = "span-links" -} - -extension ServiceContext { - var traceID: String? { - get { - self[TraceID.self] - } - set { - self[TraceID.self] = newValue - } - } - - var spanLinks: [SpanLink]? { - get { - self[ServiceContextSpanLinksKey.self] - } - set { - self[ServiceContextSpanLinksKey.self] = newValue - } - } -} - -@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *) -struct TestWriter: RPCWriterProtocol { - typealias Element = WriterElement - - private let streamContinuation: AsyncStream.Continuation - - init(streamContinuation: AsyncStream.Continuation) { - self.streamContinuation = streamContinuation - } - - func write(_ element: WriterElement) { - self.streamContinuation.yield(element) - } - - func write(contentsOf elements: some Sequence) { - elements.forEach { element in - self.write(element) - } - } -} diff --git a/Tests/GRPCProtobufCodeGenTests/ProtobufCodeGenParserTests.swift b/Tests/GRPCProtobufCodeGenTests/ProtobufCodeGenParserTests.swift deleted file mode 100644 index 6e814bb8a..000000000 --- a/Tests/GRPCProtobufCodeGenTests/ProtobufCodeGenParserTests.swift +++ /dev/null @@ -1,443 +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 GRPCCodeGen -import SwiftProtobuf -import SwiftProtobufPluginLibrary -import XCTest - -@testable import GRPCProtobufCodeGen - -final class ProtobufCodeGenParserTests: XCTestCase { - func testParser() throws { - let descriptorSet = DescriptorSet( - protos: [ - Google_Protobuf_FileDescriptorProto( - name: "same-module.proto", - package: "same-package" - ), - Google_Protobuf_FileDescriptorProto( - name: "different-module.proto", - package: "different-package" - ), - Google_Protobuf_FileDescriptorProto.helloWorld, - ] - ) - - guard let fileDescriptor = descriptorSet.fileDescriptor(named: "helloworld.proto") else { - return XCTFail( - """ - Could not find the file descriptor of "helloworld.proto". - """ - ) - } - let moduleMappings = SwiftProtobuf_GenSwift_ModuleMappings.with { - $0.mapping = [ - SwiftProtobuf_GenSwift_ModuleMappings.Entry.with { - $0.protoFilePath = ["different-module.proto"] - $0.moduleName = "DifferentModule" - } - ] - } - let parsedCodeGenRequest = try ProtobufCodeGenParser( - input: fileDescriptor, - protoFileModuleMappings: ProtoFileToModuleMappings(moduleMappingsProto: moduleMappings), - extraModuleImports: ["ExtraModule"], - accessLevel: .internal - ).parse() - - self.testCommonHelloworldParsedRequestFields(for: parsedCodeGenRequest) - - let expectedMethod = CodeGenerationRequest.ServiceDescriptor.MethodDescriptor( - documentation: "/// Sends a greeting.\n", - name: CodeGenerationRequest.Name( - base: "SayHello", - generatedUpperCase: "SayHello", - generatedLowerCase: "sayHello" - ), - isInputStreaming: false, - isOutputStreaming: false, - inputType: "Helloworld_HelloRequest", - outputType: "Helloworld_HelloReply" - ) - guard let method = parsedCodeGenRequest.services.first?.methods.first else { return XCTFail() } - XCTAssertEqual(method, expectedMethod) - - let expectedService = CodeGenerationRequest.ServiceDescriptor( - documentation: "/// The greeting service definition.\n", - name: CodeGenerationRequest.Name( - base: "Greeter", - generatedUpperCase: "Greeter", - generatedLowerCase: "greeter" - ), - namespace: CodeGenerationRequest.Name( - base: "helloworld", - generatedUpperCase: "Helloworld", - generatedLowerCase: "helloworld" - ), - methods: [expectedMethod] - ) - guard let service = parsedCodeGenRequest.services.first else { return XCTFail() } - XCTAssertEqual(service, expectedService) - XCTAssertEqual(service.methods.count, 1) - - XCTAssertEqual( - parsedCodeGenRequest.lookupSerializer("Helloworld_HelloRequest"), - "GRPCProtobuf.ProtobufSerializer()" - ) - XCTAssertEqual( - parsedCodeGenRequest.lookupDeserializer("Helloworld_HelloRequest"), - "GRPCProtobuf.ProtobufDeserializer()" - ) - } - - func testParserNestedPackage() throws { - let descriptorSet = DescriptorSet( - protos: [ - Google_Protobuf_FileDescriptorProto( - name: "same-module.proto", - package: "same-package" - ), - Google_Protobuf_FileDescriptorProto( - name: "different-module.proto", - package: "different-package" - ), - Google_Protobuf_FileDescriptorProto.helloWorldNestedPackage, - ] - ) - - guard let fileDescriptor = descriptorSet.fileDescriptor(named: "helloworld.proto") else { - return XCTFail( - """ - Could not find the file descriptor of "helloworld.proto". - """ - ) - } - let moduleMappings = SwiftProtobuf_GenSwift_ModuleMappings.with { - $0.mapping = [ - SwiftProtobuf_GenSwift_ModuleMappings.Entry.with { - $0.protoFilePath = ["different-module.proto"] - $0.moduleName = "DifferentModule" - } - ] - } - let parsedCodeGenRequest = try ProtobufCodeGenParser( - input: fileDescriptor, - protoFileModuleMappings: ProtoFileToModuleMappings(moduleMappingsProto: moduleMappings), - extraModuleImports: ["ExtraModule"], - accessLevel: .internal - ).parse() - - self.testCommonHelloworldParsedRequestFields(for: parsedCodeGenRequest) - - let expectedMethod = CodeGenerationRequest.ServiceDescriptor.MethodDescriptor( - documentation: "/// Sends a greeting.\n", - name: CodeGenerationRequest.Name( - base: "SayHello", - generatedUpperCase: "SayHello", - generatedLowerCase: "sayHello" - ), - isInputStreaming: false, - isOutputStreaming: false, - inputType: "Hello_World_HelloRequest", - outputType: "Hello_World_HelloReply" - ) - guard let method = parsedCodeGenRequest.services.first?.methods.first else { return XCTFail() } - XCTAssertEqual(method, expectedMethod) - - let expectedService = CodeGenerationRequest.ServiceDescriptor( - documentation: "/// The greeting service definition.\n", - name: CodeGenerationRequest.Name( - base: "Greeter", - generatedUpperCase: "Greeter", - generatedLowerCase: "greeter" - ), - namespace: CodeGenerationRequest.Name( - base: "hello.world", - generatedUpperCase: "Hello_World", - generatedLowerCase: "hello_world" - ), - methods: [expectedMethod] - ) - guard let service = parsedCodeGenRequest.services.first else { return XCTFail() } - XCTAssertEqual(service, expectedService) - XCTAssertEqual(service.methods.count, 1) - - XCTAssertEqual( - parsedCodeGenRequest.lookupSerializer("Hello_World_HelloRequest"), - "GRPCProtobuf.ProtobufSerializer()" - ) - XCTAssertEqual( - parsedCodeGenRequest.lookupDeserializer("Hello_World_HelloRequest"), - "GRPCProtobuf.ProtobufDeserializer()" - ) - } - - func testParserEmptyPackage() throws { - let descriptorSet = DescriptorSet( - protos: [ - Google_Protobuf_FileDescriptorProto( - name: "same-module.proto", - package: "same-package" - ), - Google_Protobuf_FileDescriptorProto( - name: "different-module.proto", - package: "different-package" - ), - Google_Protobuf_FileDescriptorProto.helloWorldEmptyPackage, - ] - ) - - guard let fileDescriptor = descriptorSet.fileDescriptor(named: "helloworld.proto") else { - return XCTFail( - """ - Could not find the file descriptor of "helloworld.proto". - """ - ) - } - let moduleMappings = SwiftProtobuf_GenSwift_ModuleMappings.with { - $0.mapping = [ - SwiftProtobuf_GenSwift_ModuleMappings.Entry.with { - $0.protoFilePath = ["different-module.proto"] - $0.moduleName = "DifferentModule" - } - ] - } - let parsedCodeGenRequest = try ProtobufCodeGenParser( - input: fileDescriptor, - protoFileModuleMappings: ProtoFileToModuleMappings(moduleMappingsProto: moduleMappings), - extraModuleImports: ["ExtraModule"], - accessLevel: .internal - ).parse() - - self.testCommonHelloworldParsedRequestFields(for: parsedCodeGenRequest) - - let expectedMethod = CodeGenerationRequest.ServiceDescriptor.MethodDescriptor( - documentation: "/// Sends a greeting.\n", - name: CodeGenerationRequest.Name( - base: "SayHello", - generatedUpperCase: "SayHello", - generatedLowerCase: "sayHello" - ), - isInputStreaming: false, - isOutputStreaming: false, - inputType: "HelloRequest", - outputType: "HelloReply" - ) - guard let method = parsedCodeGenRequest.services.first?.methods.first else { return XCTFail() } - XCTAssertEqual(method, expectedMethod) - - let expectedService = CodeGenerationRequest.ServiceDescriptor( - documentation: "/// The greeting service definition.\n", - name: CodeGenerationRequest.Name( - base: "Greeter", - generatedUpperCase: "Greeter", - generatedLowerCase: "greeter" - ), - namespace: CodeGenerationRequest.Name( - base: "", - generatedUpperCase: "", - generatedLowerCase: "" - ), - methods: [expectedMethod] - ) - guard let service = parsedCodeGenRequest.services.first else { return XCTFail() } - XCTAssertEqual(service, expectedService) - XCTAssertEqual(service.methods.count, 1) - - XCTAssertEqual( - parsedCodeGenRequest.lookupSerializer("HelloRequest"), - "GRPCProtobuf.ProtobufSerializer()" - ) - XCTAssertEqual( - parsedCodeGenRequest.lookupDeserializer("HelloRequest"), - "GRPCProtobuf.ProtobufDeserializer()" - ) - } -} - -extension ProtobufCodeGenParserTests { - func testCommonHelloworldParsedRequestFields(for request: CodeGenerationRequest) { - XCTAssertEqual(request.fileName, "helloworld.proto") - XCTAssertEqual( - request.leadingTrivia, - """ - // 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 - - """ - ) - XCTAssertEqual(request.dependencies.count, 3) - let expectedDependencyNames = ["GRPCProtobuf", "DifferentModule", "ExtraModule"] - let parsedDependencyNames = request.dependencies.map { $0.module } - XCTAssertEqual(parsedDependencyNames, expectedDependencyNames) - XCTAssertEqual(request.services.count, 1) - } -} - -extension Google_Protobuf_FileDescriptorProto { - static var helloWorld: Google_Protobuf_FileDescriptorProto { - let requestType = Google_Protobuf_DescriptorProto.with { - $0.name = "HelloRequest" - $0.field = [ - Google_Protobuf_FieldDescriptorProto.with { - $0.name = "name" - $0.number = 1 - $0.label = .optional - $0.type = .string - $0.jsonName = "name" - } - ] - } - let responseType = Google_Protobuf_DescriptorProto.with { - $0.name = "HelloReply" - $0.field = [ - Google_Protobuf_FieldDescriptorProto.with { - $0.name = "message" - $0.number = 1 - $0.label = .optional - $0.type = .string - $0.jsonName = "message" - } - ] - } - - let service = Google_Protobuf_ServiceDescriptorProto.with { - $0.name = "Greeter" - $0.method = [ - Google_Protobuf_MethodDescriptorProto.with { - $0.name = "SayHello" - $0.inputType = ".helloworld.HelloRequest" - $0.outputType = ".helloworld.HelloReply" - $0.clientStreaming = false - $0.serverStreaming = false - } - ] - } - return Google_Protobuf_FileDescriptorProto.with { - $0.name = "helloworld.proto" - $0.package = "helloworld" - $0.dependency = ["same-module.proto", "different-module.proto"] - $0.publicDependency = [0, 1] - $0.messageType = [requestType, responseType] - $0.service = [service] - $0.sourceCodeInfo = Google_Protobuf_SourceCodeInfo.with { - $0.location = [ - Google_Protobuf_SourceCodeInfo.Location.with { - $0.path = [12] - $0.span = [14, 0, 18] - $0.leadingDetachedComments = [ - """ - 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. - - """ - ] - }, - Google_Protobuf_SourceCodeInfo.Location.with { - $0.path = [6, 0] - $0.span = [19, 0, 22, 1] - $0.leadingComments = " The greeting service definition.\n" - }, - Google_Protobuf_SourceCodeInfo.Location.with { - $0.path = [6, 0, 2, 0] - $0.span = [21, 2, 53] - $0.leadingComments = " Sends a greeting.\n" - }, - ] - } - $0.syntax = "proto3" - } - } - - static var helloWorldNestedPackage: Google_Protobuf_FileDescriptorProto { - let service = Google_Protobuf_ServiceDescriptorProto.with { - $0.name = "Greeter" - $0.method = [ - Google_Protobuf_MethodDescriptorProto.with { - $0.name = "SayHello" - $0.inputType = ".hello.world.HelloRequest" - $0.outputType = ".hello.world.HelloReply" - $0.clientStreaming = false - $0.serverStreaming = false - } - ] - } - - var helloWorldCopy = self.helloWorld - helloWorldCopy.package = "hello.world" - helloWorldCopy.service = [service] - - return helloWorldCopy - } - - static var helloWorldEmptyPackage: Google_Protobuf_FileDescriptorProto { - let service = Google_Protobuf_ServiceDescriptorProto.with { - $0.name = "Greeter" - $0.method = [ - Google_Protobuf_MethodDescriptorProto.with { - $0.name = "SayHello" - $0.inputType = ".HelloRequest" - $0.outputType = ".HelloReply" - $0.clientStreaming = false - $0.serverStreaming = false - } - ] - } - var helloWorldCopy = self.helloWorld - helloWorldCopy.package = "" - helloWorldCopy.service = [service] - - return helloWorldCopy - } - - internal init(name: String, package: String) { - self.init() - self.name = name - self.package = package - } -} diff --git a/Tests/GRPCProtobufCodeGenTests/ProtobufCodeGeneratorTests.swift b/Tests/GRPCProtobufCodeGenTests/ProtobufCodeGeneratorTests.swift deleted file mode 100644 index e8c44043f..000000000 --- a/Tests/GRPCProtobufCodeGenTests/ProtobufCodeGeneratorTests.swift +++ /dev/null @@ -1,628 +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 os(macOS) || os(Linux) // swift-format doesn't like canImport(Foundation.Process) - -import GRPCCodeGen -import GRPCProtobufCodeGen -import SwiftProtobuf -import SwiftProtobufPluginLibrary -import XCTest - -final class ProtobufCodeGeneratorTests: XCTestCase { - func testProtobufCodeGenerator() throws { - try testCodeGeneration( - proto: Google_Protobuf_FileDescriptorProto.helloWorldNestedPackage, - indentation: 4, - visibility: .internal, - client: true, - server: false, - expectedCode: """ - // 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 - - internal import GRPCCore - internal import GRPCProtobuf - internal import DifferentModule - internal import ExtraModule - - internal enum Hello_World_Greeter { - internal static let descriptor = GRPCCore.ServiceDescriptor.hello_world_Greeter - internal enum Method { - internal enum SayHello { - internal typealias Input = Hello_World_HelloRequest - internal typealias Output = Hello_World_HelloReply - internal static let descriptor = GRPCCore.MethodDescriptor( - service: Hello_World_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 ClientProtocol = Hello_World_GreeterClientProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - internal typealias Client = Hello_World_GreeterClient - } - - extension GRPCCore.ServiceDescriptor { - internal static let hello_world_Greeter = Self( - package: "hello.world", - service: "Greeter" - ) - } - - /// The greeting service definition. - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - internal protocol Hello_World_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 Hello_World_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 Hello_World_Greeter.ClientProtocol { - /// Sends a greeting. - internal func sayHello( - _ message: Hello_World_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 Hello_World_GreeterClient: Hello_World_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: Hello_World_Greeter.Method.SayHello.descriptor, - serializer: serializer, - deserializer: deserializer, - options: options, - handler: body - ) - } - } - """ - ) - - try testCodeGeneration( - proto: Google_Protobuf_FileDescriptorProto.helloWorld, - indentation: 2, - visibility: .public, - client: false, - server: true, - expectedCode: """ - // 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 - - public import GRPCCore - internal import GRPCProtobuf - public import DifferentModule - public import ExtraModule - - public enum Helloworld_Greeter { - public static let descriptor = GRPCCore.ServiceDescriptor.helloworld_Greeter - public enum Method { - public enum SayHello { - public typealias Input = Helloworld_HelloRequest - public typealias Output = Helloworld_HelloReply - public static let descriptor = GRPCCore.MethodDescriptor( - service: Helloworld_Greeter.descriptor.fullyQualifiedService, - method: "SayHello" - ) - } - public static let descriptors: [GRPCCore.MethodDescriptor] = [ - SayHello.descriptor - ] - } - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public typealias StreamingServiceProtocol = Helloworld_GreeterStreamingServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - public typealias ServiceProtocol = Helloworld_GreeterServiceProtocol - } - - extension GRPCCore.ServiceDescriptor { - public 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, *) - public 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, *) - public 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, *) - public 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 { - public 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) - } - } - """ - ) - - try testCodeGeneration( - proto: Google_Protobuf_FileDescriptorProto.helloWorldEmptyPackage, - indentation: 2, - visibility: .package, - client: true, - server: true, - expectedCode: """ - // 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 - - package import GRPCCore - internal import GRPCProtobuf - package import DifferentModule - package import ExtraModule - - package enum Greeter { - package static let descriptor = GRPCCore.ServiceDescriptor.Greeter - package enum Method { - package enum SayHello { - package typealias Input = HelloRequest - package typealias Output = HelloReply - package static let descriptor = GRPCCore.MethodDescriptor( - service: Greeter.descriptor.fullyQualifiedService, - method: "SayHello" - ) - } - package static let descriptors: [GRPCCore.MethodDescriptor] = [ - SayHello.descriptor - ] - } - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - package typealias StreamingServiceProtocol = GreeterStreamingServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - package typealias ServiceProtocol = GreeterServiceProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - package typealias ClientProtocol = GreeterClientProtocol - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - package typealias Client = GreeterClient - } - - extension GRPCCore.ServiceDescriptor { - package static let Greeter = Self( - package: "", - service: "Greeter" - ) - } - - /// The greeting service definition. - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - package protocol 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 Greeter.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: 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, *) - package protocol GreeterServiceProtocol: Greeter.StreamingServiceProtocol { - /// Sends a greeting. - func sayHello( - request: GRPCCore.ServerRequest.Single, - context: GRPCCore.ServerContext - ) async throws -> GRPCCore.ServerResponse.Single - } - - /// Partial conformance to `GreeterStreamingServiceProtocol`. - @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) - extension Greeter.ServiceProtocol { - package 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, *) - package protocol 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 Greeter.ClientProtocol { - package 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 Greeter.ClientProtocol { - /// Sends a greeting. - package func sayHello( - _ message: 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, *) - package struct GreeterClient: Greeter.ClientProtocol { - private let client: GRPCCore.GRPCClient - - package init(wrapping client: GRPCCore.GRPCClient) { - self.client = client - } - - /// Sends a greeting. - package 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: Greeter.Method.SayHello.descriptor, - serializer: serializer, - deserializer: deserializer, - options: options, - handler: body - ) - } - } - """ - ) - } - - func testNoAccessLevelOnImports() throws { - let proto = Google_Protobuf_FileDescriptorProto(name: "helloworld.proto", package: "") - try testCodeGeneration( - proto: proto, - indentation: 2, - visibility: .package, - client: true, - server: true, - accessLevelOnImports: false, - expectedCode: """ - // 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 - import ExtraModule - - """ - ) - } - - func testCodeGeneration( - proto: Google_Protobuf_FileDescriptorProto, - indentation: Int, - visibility: SourceGenerator.Config.AccessLevel, - client: Bool, - server: Bool, - accessLevelOnImports: Bool = true, - expectedCode: String, - file: StaticString = #filePath, - line: UInt = #line - ) throws { - let config = SourceGenerator.Config( - accessLevel: visibility, - accessLevelOnImports: accessLevelOnImports, - client: client, - server: server, - indentation: indentation - ) - - let descriptorSet = DescriptorSet( - protos: [ - Google_Protobuf_FileDescriptorProto(name: "same-module.proto", package: "same-package"), - Google_Protobuf_FileDescriptorProto( - name: "different-module.proto", - package: "different-package" - ), - proto, - ]) - guard let fileDescriptor = descriptorSet.fileDescriptor(named: "helloworld.proto") else { - return XCTFail( - """ - Could not find the file descriptor of "helloworld.proto". - """ - ) - } - - let moduleMappings = SwiftProtobuf_GenSwift_ModuleMappings.with { - $0.mapping = [ - SwiftProtobuf_GenSwift_ModuleMappings.Entry.with { - $0.protoFilePath = ["different-module.proto"] - $0.moduleName = "DifferentModule" - } - ] - } - let generator = ProtobufCodeGenerator(configuration: config) - try XCTAssertEqualWithDiff( - try generator.generateCode( - from: fileDescriptor, - protoFileModuleMappings: ProtoFileToModuleMappings(moduleMappingsProto: moduleMappings), - extraModuleImports: ["ExtraModule"] - ), - expectedCode, - file: file, - line: line - ) - } -} - -private func diff(expected: String, actual: String) throws -> String { - let process = Process() - process.executableURL = URL(fileURLWithPath: "/usr/bin/env") - process.arguments = [ - "bash", "-c", - "diff -U5 --label=expected <(echo '\(expected)') --label=actual <(echo '\(actual)')", - ] - let pipe = Pipe() - process.standardOutput = pipe - try process.run() - process.waitUntilExit() - let pipeData = try XCTUnwrap( - pipe.fileHandleForReading.readToEnd(), - """ - No output from command: - \(process.executableURL!.path) \(process.arguments!.joined(separator: " ")) - """ - ) - return String(decoding: pipeData, as: UTF8.self) -} - -internal func XCTAssertEqualWithDiff( - _ actual: String, - _ expected: String, - file: StaticString = #filePath, - line: UInt = #line -) throws { - if actual == expected { return } - XCTFail( - """ - XCTAssertEqualWithDiff failed (click for diff) - \(try diff(expected: expected, actual: actual)) - """, - file: file, - line: line - ) -} - -#endif // os(macOS) || os(Linux) diff --git a/Tests/GRPCProtobufTests/ProtobufCodingTests.swift b/Tests/GRPCProtobufTests/ProtobufCodingTests.swift deleted file mode 100644 index dd2202af1..000000000 --- a/Tests/GRPCProtobufTests/ProtobufCodingTests.swift +++ /dev/null @@ -1,98 +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 GRPCProtobuf -import SwiftProtobuf -import XCTest - -final class ProtobufCodingTests: XCTestCase { - func testSerializeDeserializeRoundtrip() throws { - let message = Google_Protobuf_Timestamp.with { - $0.seconds = 4 - } - - let serializer = ProtobufSerializer() - let deserializer = ProtobufDeserializer() - - let bytes = try serializer.serialize(message) - let roundTrip = try deserializer.deserialize(bytes) - XCTAssertEqual(roundTrip, message) - } - - func testSerializerError() throws { - let message = TestMessage() - let serializer = ProtobufSerializer() - - XCTAssertThrowsError( - try serializer.serialize(message) - ) { error in - XCTAssertEqual( - error as? RPCError, - RPCError( - code: .invalidArgument, - message: - """ - Can't serialize message of type TestMessage. - """ - ) - ) - } - } - - func testDeserializerError() throws { - let bytes = Array("%%%%%££££".utf8) - let deserializer = ProtobufDeserializer() - XCTAssertThrowsError( - try deserializer.deserialize(bytes) - ) { error in - XCTAssertEqual( - error as? RPCError, - RPCError( - code: .invalidArgument, - message: - """ - Can't deserialize to message of type TestMessage - """ - ) - ) - } - } -} - -struct TestMessage: SwiftProtobuf.Message { - var text: String = "" - var unknownFields = SwiftProtobuf.UnknownStorage() - static let protoMessageName: String = "Test.ServiceRequest" - init() {} - - mutating func decodeMessage(decoder: inout D) throws where D: SwiftProtobuf.Decoder { - throw RPCError(code: .internalError, message: "Decoding error") - } - - func traverse(visitor: inout V) throws where V: SwiftProtobuf.Visitor { - throw RPCError(code: .internalError, message: "Traversing error") - } - - public var isInitialized: Bool { - if self.text.isEmpty { return false } - return true - } - - func isEqualTo(message: any SwiftProtobuf.Message) -> Bool { - return false - } -} diff --git a/Tests/InProcessInteroperabilityTests/InProcessInteroperabilityTests.swift b/Tests/InProcessInteroperabilityTests/InProcessInteroperabilityTests.swift deleted file mode 100644 index 437d31916..000000000 --- a/Tests/InProcessInteroperabilityTests/InProcessInteroperabilityTests.swift +++ /dev/null @@ -1,98 +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 GRPCInProcessTransport -import InteroperabilityTests -import XCTest - -@available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *) -final class InProcessInteroperabilityTests: XCTestCase { - func runInProcessTransport( - interopTestCase: InteroperabilityTestCase - ) async throws { - do { - let inProcess = InProcessTransport.makePair() - try await withThrowingTaskGroup(of: Void.self) { group in - group.addTask { - let server = GRPCServer(transport: inProcess.server, services: [TestService()]) - try await server.serve() - } - - group.addTask { - try await withThrowingTaskGroup(of: Void.self) { clientGroup in - let client = GRPCClient(transport: inProcess.client) - clientGroup.addTask { - try await client.run() - } - try await interopTestCase.makeTest().run(client: client) - - clientGroup.cancelAll() - } - } - - try await group.next() - group.cancelAll() - } - } catch let error as AssertionFailure { - XCTFail(error.message) - } - } - - func testEmptyUnary() async throws { - try await self.runInProcessTransport(interopTestCase: .emptyUnary) - } - - func testLargeUnary() async throws { - try await self.runInProcessTransport(interopTestCase: .largeUnary) - } - - func testClientStreaming() async throws { - try await self.runInProcessTransport(interopTestCase: .clientStreaming) - } - - func testServerStreaming() async throws { - try await self.runInProcessTransport(interopTestCase: .serverStreaming) - } - - func testPingPong() async throws { - try await self.runInProcessTransport(interopTestCase: .pingPong) - } - - func testEmptyStream() async throws { - try await self.runInProcessTransport(interopTestCase: .emptyStream) - } - - func testCustomMetdata() async throws { - try await self.runInProcessTransport(interopTestCase: .customMetadata) - } - - func testStatusCodeAndMessage() async throws { - try await self.runInProcessTransport(interopTestCase: .statusCodeAndMessage) - } - - func testSpecialStatusMessage() async throws { - try await self.runInProcessTransport(interopTestCase: .specialStatusMessage) - } - - func testUnimplementedMethod() async throws { - try await self.runInProcessTransport(interopTestCase: .unimplementedMethod) - } - - func testUnimplementedService() async throws { - try await self.runInProcessTransport(interopTestCase: .unimplementedService) - } -} diff --git a/scripts/license-check.sh b/scripts/license-check.sh index e006af8bc..5b8557097 100755 --- a/scripts/license-check.sh +++ b/scripts/license-check.sh @@ -107,10 +107,8 @@ check_copyright_headers() { ! -name '*.grpc.swift' \ ! -name 'LinuxMain.swift' \ ! -name 'XCTestManifests.swift' \ - ! -path './Sources/GRPCCore/Documentation.docc/*' \ ! -path './FuzzTesting/.build/*' \ ! -path './Performance/QPSBenchmark/.build/*' \ - ! -path './Performance/Benchmarks/.build/*' \ ! -path './scripts/.swift-format-source/*' \ ! -path './.build/*') }