From 34fccb6314125901ee07f43eccf8a7e2b81f08db Mon Sep 17 00:00:00 2001 From: George Barnett Date: Thu, 9 Jan 2025 17:06:02 +0000 Subject: [PATCH 1/3] Make transport generic over its bag-of-bytes type Motivation: The transport protocols deal in request/response part types. The bag-of-bytes message type used in `[UInt8]`. This means that transports might have to copy to and from the bag-of-bytes they use which is inefficient. Modifications: - Add a `GRPCContiguousBytes` protocol defining a basic bag-of-bytes type. - Make the transport protocols have an associated `Bytes` type which conforms to `GRPCContiguousBytes`. - Propagate this requirement throughout the codebase; this affects the generated code. - Update the code generator to generate the appropriate code. - Update tests Result: - Transports can use a bag-of-bytes type of their choosing. --- .../Internal/StructuredSwift+Client.swift | 25 ++++++-- .../Internal/StructuredSwift+Server.swift | 8 ++- .../Internal/StructuredSwift+Types.swift | 11 +++- .../ClientRPCExecutor+RetryExecutor.swift | 7 ++- .../Client/Internal/ClientRPCExecutor.swift | 7 ++- .../Internal/ClientStreamExecutor.swift | 27 +++++---- .../Server/Internal/ServerRPCExecutor.swift | 36 ++++++------ Sources/GRPCCore/Call/Server/RPCRouter.swift | 14 ++--- .../Call/Server/RegistrableRPCService.swift | 2 +- Sources/GRPCCore/Coding/Coding.swift | 4 +- .../GRPCCore/Coding/GRPCContiguousBytes.swift | 58 +++++++++++++++++++ Sources/GRPCCore/GRPCClient.swift | 20 +++---- Sources/GRPCCore/GRPCServer.swift | 28 ++++----- .../RPCWriter+MessageToRPCResponsePart.swift | 13 +++-- .../Internal/RPCWriter+Serialize.swift | 5 +- .../GRPCCore/Transport/ClientTransport.swift | 9 ++- Sources/GRPCCore/Transport/RPCParts.swift | 16 +++-- .../GRPCCore/Transport/ServerTransport.swift | 9 ++- .../InProcessTransport+Client.swift | 15 ++--- .../InProcessTransport+Server.swift | 6 +- .../StructuredSwift+ClientTests.swift | 6 +- .../StructuredSwift+ServerTests.swift | 2 +- ...lientCodeTranslatorSnippetBasedTests.swift | 6 +- ...uredSwiftTranslatorSnippetBasedTests.swift | 2 +- ...erverCodeTranslatorSnippetBasedTests.swift | 2 +- ...PCExecutorTestHarness+ServerBehavior.swift | 14 ++--- .../ServerRPCExecutorTestHarness.swift | 21 +++---- .../Internal/ServerRPCExecutorTests.swift | 5 ++ .../Call/Server/RPCRouterTests.swift | 21 ++++++- Tests/GRPCCoreTests/Coding/CodingTests.swift | 2 +- Tests/GRPCCoreTests/GRPCClientTests.swift | 26 +++------ Tests/GRPCCoreTests/GRPCServerTests.swift | 20 +++---- Tests/GRPCCoreTests/RPCPartsTests.swift | 4 +- .../Test Utilities/Coding+Identity.swift | 10 ++-- .../Test Utilities/Coding+JSON.swift | 10 ++-- .../Test Utilities/Services/BinaryEcho.swift | 2 +- .../Test Utilities/Services/HelloWorld.swift | 2 +- .../Transport/AnyTransport.swift | 11 ++-- .../Transport/StreamCountingTransport.swift | 11 ++-- .../Transport/ThrowingTransport.swift | 7 ++- .../Test Utilities/XCTest+Utilities.swift | 24 ++++---- .../InProcessServerTransportTests.swift | 18 +++--- .../InProcessTransportTests.swift | 23 +++++--- 43 files changed, 354 insertions(+), 215 deletions(-) create mode 100644 Sources/GRPCCore/Coding/GRPCContiguousBytes.swift diff --git a/Sources/GRPCCodeGen/Internal/StructuredSwift+Client.swift b/Sources/GRPCCodeGen/Internal/StructuredSwift+Client.swift index 6c026f2d3..4e9cc905b 100644 --- a/Sources/GRPCCodeGen/Internal/StructuredSwift+Client.swift +++ b/Sources/GRPCCodeGen/Internal/StructuredSwift+Client.swift @@ -645,10 +645,10 @@ extension FunctionDescription { extension StructDescription { /// ``` - /// struct : { - /// private let client: GRPCCore.GRPCClient + /// struct : where Transport: GRPCCore.ClientTransport { + /// private let client: GRPCCore.GRPCClient /// - /// init(wrapping client: GRPCCore.GRPCClient) { + /// init(wrapping client: GRPCCore.GRPCClient) { /// self.client = client /// } /// @@ -665,9 +665,18 @@ extension StructDescription { StructDescription( accessModifier: accessLevel, name: name, + generics: [.member("Transport")], conformances: [clientProtocol], + whereClause: WhereClause( + requirements: [.conformance("Transport", "GRPCCore.ClientTransport")] + ), members: [ - .variable(accessModifier: .private, kind: .let, left: "client", type: .grpcClient), + .variable( + accessModifier: .private, + kind: .let, + left: "client", + type: .grpcClient(genericOver: "Transport") + ), .commentable( .preFormatted( """ @@ -681,7 +690,13 @@ extension StructDescription { accessModifier: accessLevel, kind: .initializer, parameters: [ - ParameterDescription(label: "wrapping", name: "client", type: .grpcClient) + ParameterDescription( + label: "wrapping", + name: "client", + type: .grpcClient( + genericOver: "Transport" + ) + ) ], whereClause: nil, body: [ diff --git a/Sources/GRPCCodeGen/Internal/StructuredSwift+Server.swift b/Sources/GRPCCodeGen/Internal/StructuredSwift+Server.swift index 95643d4d0..7af446a5e 100644 --- a/Sources/GRPCCodeGen/Internal/StructuredSwift+Server.swift +++ b/Sources/GRPCCodeGen/Internal/StructuredSwift+Server.swift @@ -311,14 +311,20 @@ extension FunctionDescription { return FunctionDescription( accessModifier: accessLevel, kind: .function(name: "registerMethods"), + generics: [.member("Transport")], parameters: [ ParameterDescription( label: "with", name: "router", - type: .rpcRouter, + type: .rpcRouter(genericOver: "Transport"), `inout`: true ) ], + whereClause: WhereClause( + requirements: [ + .conformance("Transport", "GRPCCore.ServerTransport") + ] + ), body: methods.map { method in .functionCall( .registerWithRouter( diff --git a/Sources/GRPCCodeGen/Internal/StructuredSwift+Types.swift b/Sources/GRPCCodeGen/Internal/StructuredSwift+Types.swift index 4706e9888..86abaaf71 100644 --- a/Sources/GRPCCodeGen/Internal/StructuredSwift+Types.swift +++ b/Sources/GRPCCodeGen/Internal/StructuredSwift+Types.swift @@ -54,7 +54,11 @@ extension ExistingTypeDescription { } package static let serverContext: Self = .grpcCore("ServerContext") - package static let rpcRouter: Self = .grpcCore("RPCRouter") + + package static func rpcRouter(genericOver type: String) -> Self { + .generic(wrapper: .grpcCore("RPCRouter"), wrapped: .member(type)) + } + package static let serviceDescriptor: Self = .grpcCore("ServiceDescriptor") package static let methodDescriptor: Self = .grpcCore("MethodDescriptor") @@ -80,5 +84,8 @@ extension ExistingTypeDescription { package static let callOptions: Self = .grpcCore("CallOptions") package static let metadata: Self = .grpcCore("Metadata") - package static let grpcClient: Self = .grpcCore("GRPCClient") + + package static func grpcClient(genericOver transport: String) -> Self { + .generic(wrapper: .grpcCore("GRPCClient"), wrapped: [.member(transport)]) + } } diff --git a/Sources/GRPCCore/Call/Client/Internal/ClientRPCExecutor+RetryExecutor.swift b/Sources/GRPCCore/Call/Client/Internal/ClientRPCExecutor+RetryExecutor.swift index 6e7da3433..4740cc033 100644 --- a/Sources/GRPCCore/Call/Client/Internal/ClientRPCExecutor+RetryExecutor.swift +++ b/Sources/GRPCCore/Call/Client/Internal/ClientRPCExecutor+RetryExecutor.swift @@ -193,8 +193,11 @@ extension ClientRPCExecutor.RetryExecutor { } @inlinable - func executeAttempt( - stream: RPCStream, + func executeAttempt( + stream: RPCStream< + RPCAsyncSequence, any Error>, + RPCWriter>.Closable + >, metadata: Metadata, retryStream: BroadcastAsyncSequence, method: MethodDescriptor, diff --git a/Sources/GRPCCore/Call/Client/Internal/ClientRPCExecutor.swift b/Sources/GRPCCore/Call/Client/Internal/ClientRPCExecutor.swift index ade536d65..94a7f9ed5 100644 --- a/Sources/GRPCCore/Call/Client/Internal/ClientRPCExecutor.swift +++ b/Sources/GRPCCore/Call/Client/Internal/ClientRPCExecutor.swift @@ -112,7 +112,7 @@ extension ClientRPCExecutor { /// interceptors will be called in the order of the array. /// - Returns: The deserialized response. @inlinable // would be private - static func _execute( + static func _execute( in group: inout TaskGroup, request: StreamingClientRequest, method: MethodDescriptor, @@ -120,7 +120,10 @@ extension ClientRPCExecutor { serializer: some MessageSerializer, deserializer: some MessageDeserializer, interceptors: [any ClientInterceptor], - stream: RPCStream + stream: RPCStream< + RPCAsyncSequence, any Error>, + RPCWriter>.Closable + > ) async -> StreamingClientResponse { let context = ClientContext(descriptor: method) diff --git a/Sources/GRPCCore/Call/Client/Internal/ClientStreamExecutor.swift b/Sources/GRPCCore/Call/Client/Internal/ClientStreamExecutor.swift index f4cf1c482..1b2cac862 100644 --- a/Sources/GRPCCore/Call/Client/Internal/ClientStreamExecutor.swift +++ b/Sources/GRPCCore/Call/Client/Internal/ClientStreamExecutor.swift @@ -28,14 +28,17 @@ internal enum ClientStreamExecutor { /// - stream: The stream to excecute the RPC on. /// - Returns: A streamed response. @inlinable - static func execute( + static func execute( in group: inout TaskGroup, request: StreamingClientRequest, context: ClientContext, attempt: Int, serializer: some MessageSerializer, deserializer: some MessageDeserializer, - stream: RPCStream + stream: RPCStream< + RPCAsyncSequence, any Error>, + RPCWriter>.Closable + > ) async -> StreamingClientResponse { // Let the server know this is a retry. var metadata = request.metadata @@ -83,8 +86,8 @@ internal enum ClientStreamExecutor { } @inlinable // would be private - static func _processRequest( - on stream: some ClosableRPCWriterProtocol, + static func _processRequest( + on stream: some ClosableRPCWriterProtocol>, request: StreamingClientRequest, serializer: some MessageSerializer ) async { @@ -104,16 +107,19 @@ internal enum ClientStreamExecutor { } @usableFromInline - enum OnFirstResponsePart: Sendable { - case metadata(Metadata, UnsafeTransfer) + enum OnFirstResponsePart: Sendable { + case metadata( + Metadata, + UnsafeTransfer, any Error>.AsyncIterator> + ) case status(Status, Metadata) case failed(RPCError) } @inlinable // would be private - static func _waitForFirstResponsePart( - on stream: ClientTransport.Inbound - ) async -> OnFirstResponsePart { + static func _waitForFirstResponsePart( + on stream: RPCAsyncSequence, any Error> + ) async -> OnFirstResponsePart { var iterator = stream.makeAsyncIterator() let result = await Result { switch try await iterator.next() { @@ -165,7 +171,8 @@ internal enum ClientStreamExecutor { @usableFromInline struct RawBodyPartToMessageSequence< - Base: AsyncSequence, + Base: AsyncSequence, Failure>, + Bytes: GRPCContiguousBytes, Message: Sendable, Deserializer: MessageDeserializer, Failure: Error diff --git a/Sources/GRPCCore/Call/Server/Internal/ServerRPCExecutor.swift b/Sources/GRPCCore/Call/Server/Internal/ServerRPCExecutor.swift index 50ff0b3bd..e2184de14 100644 --- a/Sources/GRPCCore/Call/Server/Internal/ServerRPCExecutor.swift +++ b/Sources/GRPCCore/Call/Server/Internal/ServerRPCExecutor.swift @@ -27,11 +27,11 @@ struct ServerRPCExecutor { /// interceptors will be called in the order of the array. /// - handler: A handler which turns the request into a response. @inlinable - static func execute( + static func execute( context: ServerContext, stream: RPCStream< - RPCAsyncSequence, - RPCWriter.Closable + RPCAsyncSequence, any Error>, + RPCWriter>.Closable >, deserializer: some MessageDeserializer, serializer: some MessageSerializer, @@ -66,11 +66,11 @@ struct ServerRPCExecutor { } @inlinable - static func _execute( + static func _execute( context: ServerContext, metadata: Metadata, - inbound: UnsafeTransfer.AsyncIterator>, - outbound: RPCWriter.Closable, + inbound: UnsafeTransfer, any Error>.AsyncIterator>, + outbound: RPCWriter>.Closable, deserializer: some MessageDeserializer, serializer: some MessageSerializer, interceptors: [any ServerInterceptor], @@ -106,12 +106,12 @@ struct ServerRPCExecutor { } @inlinable - static func _processRPCWithTimeout( + static func _processRPCWithTimeout( timeout: Duration, context: ServerContext, metadata: Metadata, - inbound: UnsafeTransfer.AsyncIterator>, - outbound: RPCWriter.Closable, + inbound: UnsafeTransfer, any Error>.AsyncIterator>, + outbound: RPCWriter>.Closable, deserializer: some MessageDeserializer, serializer: some MessageSerializer, interceptors: [any ServerInterceptor], @@ -147,11 +147,11 @@ struct ServerRPCExecutor { } @inlinable - static func _processRPC( + static func _processRPC( context: ServerContext, metadata: Metadata, - inbound: UnsafeTransfer.AsyncIterator>, - outbound: RPCWriter.Closable, + inbound: UnsafeTransfer, any Error>.AsyncIterator>, + outbound: RPCWriter>.Closable, deserializer: some MessageDeserializer, serializer: some MessageSerializer, interceptors: [any ServerInterceptor], @@ -235,12 +235,12 @@ struct ServerRPCExecutor { } @inlinable - static func _waitForFirstRequestPart( - inbound: RPCAsyncSequence - ) async -> OnFirstRequestPart { + static func _waitForFirstRequestPart( + inbound: RPCAsyncSequence, any Error> + ) async -> OnFirstRequestPart { var iterator = inbound.makeAsyncIterator() let part = await Result { try await iterator.next() } - let onFirstRequestPart: OnFirstRequestPart + let onFirstRequestPart: OnFirstRequestPart switch part { case .success(.metadata(let metadata)): @@ -275,10 +275,10 @@ struct ServerRPCExecutor { } @usableFromInline - enum OnFirstRequestPart { + enum OnFirstRequestPart { case process( Metadata, - UnsafeTransfer.AsyncIterator> + UnsafeTransfer, any Error>.AsyncIterator> ) case reject(RPCError) } diff --git a/Sources/GRPCCore/Call/Server/RPCRouter.swift b/Sources/GRPCCore/Call/Server/RPCRouter.swift index d40bd71c4..f6f0a7f79 100644 --- a/Sources/GRPCCore/Call/Server/RPCRouter.swift +++ b/Sources/GRPCCore/Call/Server/RPCRouter.swift @@ -34,15 +34,15 @@ /// 1. Remove individual methods by calling ``removeHandler(forMethod:)``, or /// 2. Implement ``RegistrableRPCService/registerMethods(with:)`` to register only the methods you /// want to be served. -public struct RPCRouter: Sendable { +public struct RPCRouter: Sendable { @usableFromInline struct RPCHandler: Sendable { @usableFromInline let _fn: @Sendable ( _ stream: RPCStream< - RPCAsyncSequence, - RPCWriter.Closable + RPCAsyncSequence, any Error>, + RPCWriter>.Closable >, _ context: ServerContext, _ interceptors: [any ServerInterceptor] @@ -73,8 +73,8 @@ public struct RPCRouter: Sendable { @inlinable func handle( stream: RPCStream< - RPCAsyncSequence, - RPCWriter.Closable + RPCAsyncSequence, any Error>, + RPCWriter>.Closable >, context: ServerContext, interceptors: [any ServerInterceptor] @@ -170,8 +170,8 @@ public struct RPCRouter: Sendable { extension RPCRouter { internal func handle( stream: RPCStream< - RPCAsyncSequence, - RPCWriter.Closable + RPCAsyncSequence, any Error>, + RPCWriter>.Closable >, context: ServerContext ) async { diff --git a/Sources/GRPCCore/Call/Server/RegistrableRPCService.swift b/Sources/GRPCCore/Call/Server/RegistrableRPCService.swift index f2a9bee03..b7f3e241b 100644 --- a/Sources/GRPCCore/Call/Server/RegistrableRPCService.swift +++ b/Sources/GRPCCore/Call/Server/RegistrableRPCService.swift @@ -26,5 +26,5 @@ 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) + func registerMethods(with router: inout RPCRouter) } diff --git a/Sources/GRPCCore/Coding/Coding.swift b/Sources/GRPCCore/Coding/Coding.swift index 29569d3c0..97e236db3 100644 --- a/Sources/GRPCCore/Coding/Coding.swift +++ b/Sources/GRPCCore/Coding/Coding.swift @@ -30,7 +30,7 @@ public protocol MessageSerializer: Sendable { /// /// - Parameter message: The message to serialize. /// - Returns: The serialized bytes of a message. - func serialize(_ message: Message) throws -> [UInt8] + func serialize(_ message: Message) throws -> Bytes } /// Deserializes a sequence of bytes into a message. @@ -49,5 +49,5 @@ public protocol MessageDeserializer: Sendable { /// /// - Parameter serializedMessageBytes: The bytes to deserialize. /// - Returns: The deserialized message. - func deserialize(_ serializedMessageBytes: [UInt8]) throws -> Message + func deserialize(_ serializedMessageBytes: Bytes) throws -> Message } diff --git a/Sources/GRPCCore/Coding/GRPCContiguousBytes.swift b/Sources/GRPCCore/Coding/GRPCContiguousBytes.swift new file mode 100644 index 000000000..93adb45fb --- /dev/null +++ b/Sources/GRPCCore/Coding/GRPCContiguousBytes.swift @@ -0,0 +1,58 @@ +/* + * Copyright 2025, gRPC Authors All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 bag-of-bytes type. +/// +/// This protocol is used by the transport protocols (``ClientTransport`` and ``ServerTransport``) +/// with the serialization protocols (``MessageSerializer`` and ``MessageDeserializer``) so that +/// messages don't have to be copied to a fixed intermediate bag-of-bytes types. +public protocol GRPCContiguousBytes { + /// Initialize the bytes to a repeated value. + /// + /// - Parameters: + /// - byte: The value to be repeated. + /// - count: The number of times to repeat the byte value. + init(repeating byte: UInt8, count: Int) + + /// Initialize the bag of bytes from a sequence of bytes. + /// + /// - Parameters: + /// - sequence: a sequence of `UInt8` from which the bag of bytes should be constructed. + init(_ sequence: Bytes) where Bytes.Element == UInt8 + + /// The number of bytes in the bag of bytes. + var count: Int { get } + + /// Calls the given closure with the contents of underlying storage. + /// + /// - Note: Calling `withUnsafeBytes` multiple times does not guarantee that + /// the same buffer pointer will be passed in every time. + /// - Warning: The buffer argument to the body should not be stored or used + /// outside of the lifetime of the call to the closure. + func withUnsafeBytes(_ body: (_ buffer: UnsafeRawBufferPointer) throws -> R) rethrows -> R + + /// Calls the given closure with the contents of underlying storage. + /// + /// - Note: Calling `withUnsafeBytes` multiple times does not guarantee that + /// the same buffer pointer will be passed in every time. + /// - Warning: The buffer argument to the body should not be stored or used + /// outside of the lifetime of the call to the closure. + mutating func withUnsafeMutableBytes( + _ body: (_ buffer: UnsafeMutableRawBufferPointer) throws -> R + ) rethrows -> R +} + +extension [UInt8]: GRPCContiguousBytes {} diff --git a/Sources/GRPCCore/GRPCClient.swift b/Sources/GRPCCore/GRPCClient.swift index b9d3234b1..50104682a 100644 --- a/Sources/GRPCCore/GRPCClient.swift +++ b/Sources/GRPCCore/GRPCClient.swift @@ -54,9 +54,9 @@ private import Synchronization /// 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). -public final class GRPCClient: Sendable { +public final class GRPCClient: Sendable { /// The transport which provides a bidirectional communication channel with the server. - private let transport: any ClientTransport + private let transport: Transport /// The current state of the client. private let stateMachine: Mutex @@ -175,7 +175,7 @@ public final class GRPCClient: Sendable { /// request. The last interceptor added will be the final interceptor to intercept each /// request before calling the appropriate handler. convenience public init( - transport: some ClientTransport, + transport: Transport, interceptors: [any ClientInterceptor] = [] ) { self.init( @@ -194,7 +194,7 @@ public final class GRPCClient: Sendable { /// 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, + transport: Transport, interceptorPipeline: [ClientInterceptorPipelineOperation] ) { self.transport = transport @@ -393,11 +393,11 @@ public final class GRPCClient: Sendable { /// code is nonisolated. /// - handleClient: A closure which is called with the client. When the closure returns, the /// client is shutdown gracefully. -public func withGRPCClient( - transport: some ClientTransport, +public func withGRPCClient( + transport: Transport, interceptors: [any ClientInterceptor] = [], isolation: isolated (any Actor)? = #isolation, - handleClient: (GRPCClient) async throws -> Result + handleClient: (GRPCClient) async throws -> Result ) async throws -> Result { try await withGRPCClient( transport: transport, @@ -421,11 +421,11 @@ public func withGRPCClient( /// - handleClient: A closure which is called with the client. When the closure returns, the /// client is shutdown gracefully. /// - Returns: The result of the `handleClient` closure. -public func withGRPCClient( - transport: some ClientTransport, +public func withGRPCClient( + transport: Transport, interceptorPipeline: [ClientInterceptorPipelineOperation], isolation: isolated (any Actor)? = #isolation, - handleClient: (GRPCClient) async throws -> Result + handleClient: (GRPCClient) async throws -> Result ) async throws -> Result { try await withThrowingDiscardingTaskGroup { group in let client = GRPCClient(transport: transport, interceptorPipeline: interceptorPipeline) diff --git a/Sources/GRPCCore/GRPCServer.swift b/Sources/GRPCCore/GRPCServer.swift index f8f576e65..96c2043bc 100644 --- a/Sources/GRPCCore/GRPCServer.swift +++ b/Sources/GRPCCore/GRPCServer.swift @@ -74,14 +74,14 @@ private import Synchronization /// 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). -public final class GRPCServer: Sendable { - typealias Stream = RPCStream +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 + public let transport: Transport /// The services registered which the server is serving. - private let router: RPCRouter + private let router: RPCRouter /// The state of the server. private let state: Mutex @@ -147,7 +147,7 @@ public final class GRPCServer: Sendable { /// 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, + transport: Transport, services: [any RegistrableRPCService], interceptors: [any ServerInterceptor] = [] ) { @@ -169,11 +169,11 @@ public final class GRPCServer: Sendable { /// 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, + transport: Transport, services: [any RegistrableRPCService], interceptorPipeline: [ServerInterceptorPipelineOperation] ) { - var router = RPCRouter() + var router = RPCRouter() for service in services { service.registerMethods(with: &router) } @@ -187,7 +187,7 @@ public final class GRPCServer: Sendable { /// - Parameters: /// - transport: The transport the server should listen on. /// - router: A ``RPCRouter`` used by the server to route accepted streams to method handlers. - public init(transport: any ServerTransport, router: RPCRouter) { + public init(transport: Transport, router: RPCRouter) { self.state = Mutex(.notStarted) self.transport = transport self.router = router @@ -256,12 +256,12 @@ public final class GRPCServer: Sendable { /// - handleServer: A closure which is called with the server. When the closure returns, the /// server is shutdown gracefully. /// - Returns: The result of the `handleServer` closure. -public func withGRPCServer( - transport: any ServerTransport, +public func withGRPCServer( + transport: Transport, services: [any RegistrableRPCService], interceptors: [any ServerInterceptor] = [], isolation: isolated (any Actor)? = #isolation, - handleServer: (GRPCServer) async throws -> Result + handleServer: (GRPCServer) async throws -> Result ) async throws -> Result { try await withGRPCServer( transport: transport, @@ -287,12 +287,12 @@ public func withGRPCServer( /// - handleServer: A closure which is called with the server. When the closure returns, the /// server is shutdown gracefully. /// - Returns: The result of the `handleServer` closure. -public func withGRPCServer( - transport: any ServerTransport, +public func withGRPCServer( + transport: Transport, services: [any RegistrableRPCService], interceptorPipeline: [ServerInterceptorPipelineOperation], isolation: isolated (any Actor)? = #isolation, - handleServer: (GRPCServer) async throws -> Result + handleServer: (GRPCServer) async throws -> Result ) async throws -> Result { return try await withThrowingDiscardingTaskGroup { group in let server = GRPCServer( diff --git a/Sources/GRPCCore/Streaming/Internal/RPCWriter+MessageToRPCResponsePart.swift b/Sources/GRPCCore/Streaming/Internal/RPCWriter+MessageToRPCResponsePart.swift index 15dd4b6cd..84b2f70f3 100644 --- a/Sources/GRPCCore/Streaming/Internal/RPCWriter+MessageToRPCResponsePart.swift +++ b/Sources/GRPCCore/Streaming/Internal/RPCWriter+MessageToRPCResponsePart.swift @@ -16,18 +16,19 @@ @usableFromInline struct MessageToRPCResponsePartWriter< - Serializer: MessageSerializer + Serializer: MessageSerializer, + Bytes: GRPCContiguousBytes & Sendable >: RPCWriterProtocol where Serializer.Message: Sendable { @usableFromInline typealias Element = Serializer.Message @usableFromInline - let base: RPCWriter + let base: RPCWriter> @usableFromInline let serializer: Serializer @inlinable - init(serializer: Serializer, base: some RPCWriterProtocol) { + init(serializer: Serializer, base: some RPCWriterProtocol>) { self.serializer = serializer self.base = RPCWriter(wrapping: base) } @@ -39,7 +40,7 @@ struct MessageToRPCResponsePartWriter< @inlinable func write(contentsOf elements: some Sequence) async throws { - let requestParts = try elements.map { message -> RPCResponsePart in + let requestParts = try elements.map { message -> RPCResponsePart in .message(try self.serializer.serialize(message)) } @@ -49,8 +50,8 @@ struct MessageToRPCResponsePartWriter< extension RPCWriter { @inlinable - static func serializingToRPCResponsePart( - into writer: some RPCWriterProtocol, + 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 index d3d93d74d..1eff0c09a 100644 --- a/Sources/GRPCCore/Streaming/Internal/RPCWriter+Serialize.swift +++ b/Sources/GRPCCore/Streaming/Internal/RPCWriter+Serialize.swift @@ -16,7 +16,8 @@ @usableFromInline struct SerializingRPCWriter< - Base: RPCWriterProtocol<[UInt8]>, + Base: RPCWriterProtocol, + Bytes: GRPCContiguousBytes, Serializer: MessageSerializer >: RPCWriterProtocol where Serializer.Message: Sendable { @usableFromInline @@ -41,7 +42,7 @@ struct SerializingRPCWriter< @inlinable func write(contentsOf elements: some Sequence) async throws { let requestParts = try elements.map { message in - try self.serializer.serialize(message) + try self.serializer.serialize(message) as Bytes } try await self.base.write(contentsOf: requestParts) diff --git a/Sources/GRPCCore/Transport/ClientTransport.swift b/Sources/GRPCCore/Transport/ClientTransport.swift index a86a79fea..ce68b4559 100644 --- a/Sources/GRPCCore/Transport/ClientTransport.swift +++ b/Sources/GRPCCore/Transport/ClientTransport.swift @@ -14,9 +14,12 @@ * limitations under the License. */ -public protocol ClientTransport: Sendable { - typealias Inbound = RPCAsyncSequence - typealias Outbound = RPCWriter.Closable +public protocol ClientTransport: Sendable { + /// The bag-of-bytes type used by the transport. + associatedtype Bytes: GRPCContiguousBytes & Sendable + + typealias Inbound = RPCAsyncSequence, any Error> + typealias Outbound = RPCWriter>.Closable /// Returns a throttle which gRPC uses to determine whether retries can be executed. /// diff --git a/Sources/GRPCCore/Transport/RPCParts.swift b/Sources/GRPCCore/Transport/RPCParts.swift index cd72f7efb..ba19d58b4 100644 --- a/Sources/GRPCCore/Transport/RPCParts.swift +++ b/Sources/GRPCCore/Transport/RPCParts.swift @@ -15,7 +15,7 @@ */ /// Part of a request sent from a client to a server in a stream. -public enum RPCRequestPart: Hashable, Sendable { +public enum RPCRequestPart { /// Key-value pairs sent at the start of a request stream. Only one ``metadata(_:)`` value may /// be sent to the server. case metadata(Metadata) @@ -23,11 +23,15 @@ public enum RPCRequestPart: Hashable, Sendable { /// 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]) + case message(Bytes) } +extension RPCRequestPart: Sendable where Bytes: Sendable {} +extension RPCRequestPart: Hashable where Bytes: Hashable {} +extension RPCRequestPart: Equatable where Bytes: Equatable {} + /// Part of a response sent from a server to a client in a stream. -public enum RPCResponsePart: Hashable, Sendable { +public enum RPCResponsePart { /// 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. @@ -36,10 +40,14 @@ public enum RPCResponsePart: Hashable, Sendable { /// 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]) + case message(Bytes) /// 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) } + +extension RPCResponsePart: Sendable where Bytes: Sendable {} +extension RPCResponsePart: Hashable where Bytes: Hashable {} +extension RPCResponsePart: Equatable where Bytes: Equatable {} diff --git a/Sources/GRPCCore/Transport/ServerTransport.swift b/Sources/GRPCCore/Transport/ServerTransport.swift index 15148c78e..cda396d77 100644 --- a/Sources/GRPCCore/Transport/ServerTransport.swift +++ b/Sources/GRPCCore/Transport/ServerTransport.swift @@ -15,9 +15,12 @@ */ /// A protocol server transport implementations must conform to. -public protocol ServerTransport: Sendable { - typealias Inbound = RPCAsyncSequence - typealias Outbound = RPCWriter.Closable +public protocol ServerTransport: Sendable { + /// The bag-of-bytes type used by the transport. + associatedtype Bytes: GRPCContiguousBytes & Sendable + + typealias Inbound = RPCAsyncSequence, any Error> + typealias Outbound = RPCWriter>.Closable /// Starts the transport. /// diff --git a/Sources/GRPCInProcessTransport/InProcessTransport+Client.swift b/Sources/GRPCInProcessTransport/InProcessTransport+Client.swift index b24ec07e1..093f2dbdc 100644 --- a/Sources/GRPCInProcessTransport/InProcessTransport+Client.swift +++ b/Sources/GRPCInProcessTransport/InProcessTransport+Client.swift @@ -36,6 +36,8 @@ extension InProcessTransport { /// /// - SeeAlso: `ClientTransport` public final class Client: ClientTransport { + public typealias Bytes = [UInt8] + private enum State: Sendable { struct UnconnectedState { var serverTransport: InProcessTransport.Server @@ -54,7 +56,8 @@ extension InProcessTransport { [Int: ( RPCStream, RPCStream< - RPCAsyncSequence, RPCWriter.Closable + RPCAsyncSequence, any Error>, + RPCWriter>.Closable > )] var signalEndContinuation: AsyncStream.Continuation @@ -75,7 +78,8 @@ extension InProcessTransport { [Int: ( RPCStream, RPCStream< - RPCAsyncSequence, RPCWriter.Closable + RPCAsyncSequence, any Error>, + RPCWriter>.Closable > )] var signalEndContinuation: AsyncStream.Continuation? @@ -96,9 +100,6 @@ extension InProcessTransport { case closed(ClosedState) } - public typealias Inbound = RPCAsyncSequence - public typealias Outbound = RPCWriter.Closable - public let retryThrottle: RetryThrottle? private let methodConfig: MethodConfigs @@ -232,8 +233,8 @@ extension InProcessTransport { 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 request = GRPCAsyncThrowingStream.makeStream(of: RPCRequestPart.self) + let response = GRPCAsyncThrowingStream.makeStream(of: RPCResponsePart.self) let clientStream = RPCStream( descriptor: descriptor, diff --git a/Sources/GRPCInProcessTransport/InProcessTransport+Server.swift b/Sources/GRPCInProcessTransport/InProcessTransport+Server.swift index 90e291b6e..cc3b17585 100644 --- a/Sources/GRPCInProcessTransport/InProcessTransport+Server.swift +++ b/Sources/GRPCInProcessTransport/InProcessTransport+Server.swift @@ -29,8 +29,10 @@ extension InProcessTransport { /// /// - SeeAlso: `ClientTransport` public final class Server: ServerTransport, Sendable { - public typealias Inbound = RPCAsyncSequence - public typealias Outbound = RPCWriter.Closable + public typealias Bytes = [UInt8] + + public typealias Inbound = RPCAsyncSequence, any Error> + public typealias Outbound = RPCWriter>.Closable private let newStreams: AsyncStream> private let newStreamsContinuation: AsyncStream>.Continuation diff --git a/Tests/GRPCCodeGenTests/Internal/StructuredSwift+ClientTests.swift b/Tests/GRPCCodeGenTests/Internal/StructuredSwift+ClientTests.swift index caa8c9bc5..6404a751a 100644 --- a/Tests/GRPCCodeGenTests/Internal/StructuredSwift+ClientTests.swift +++ b/Tests/GRPCCodeGenTests/Internal/StructuredSwift+ClientTests.swift @@ -489,14 +489,14 @@ extension StructuredSwiftTests { ) let expected = """ - \(access) struct FooClient: Foo_ClientProtocol { - private let client: GRPCCore.GRPCClient + \(access) struct FooClient: Foo_ClientProtocol where Transport: GRPCCore.ClientTransport { + private let client: GRPCCore.GRPCClient /// Creates a new client wrapping the provided `GRPCCore.GRPCClient`. /// /// - Parameters: /// - client: A `GRPCCore.GRPCClient` providing a communication channel to the service. - \(access) init(wrapping client: GRPCCore.GRPCClient) { + \(access) init(wrapping client: GRPCCore.GRPCClient) { self.client = client } diff --git a/Tests/GRPCCodeGenTests/Internal/StructuredSwift+ServerTests.swift b/Tests/GRPCCodeGenTests/Internal/StructuredSwift+ServerTests.swift index a415307a6..ba44a6896 100644 --- a/Tests/GRPCCodeGenTests/Internal/StructuredSwift+ServerTests.swift +++ b/Tests/GRPCCodeGenTests/Internal/StructuredSwift+ServerTests.swift @@ -220,7 +220,7 @@ extension StructuredSwiftTests { } let expected = """ - \(access) func registerMethods(with router: inout GRPCCore.RPCRouter) { + \(access) func registerMethods(with router: inout GRPCCore.RPCRouter) where Transport: GRPCCore.ServerTransport { router.registerHandler( forMethod: FooService.Method.Bar.descriptor, deserializer: Deserialize(), diff --git a/Tests/GRPCCodeGenTests/Internal/Translator/ClientCodeTranslatorSnippetBasedTests.swift b/Tests/GRPCCodeGenTests/Internal/Translator/ClientCodeTranslatorSnippetBasedTests.swift index 8c40eb7f4..7d1d7504d 100644 --- a/Tests/GRPCCodeGenTests/Internal/Translator/ClientCodeTranslatorSnippetBasedTests.swift +++ b/Tests/GRPCCodeGenTests/Internal/Translator/ClientCodeTranslatorSnippetBasedTests.swift @@ -82,14 +82,14 @@ struct ClientCodeTranslatorSnippetBasedTests { /// > Source IDL Documentation: /// > /// > Documentation for ServiceA - public struct Client: ClientProtocol { - private let client: GRPCCore.GRPCClient + public struct Client: ClientProtocol where Transport: GRPCCore.ClientTransport { + private let client: GRPCCore.GRPCClient /// Creates a new client wrapping the provided `GRPCCore.GRPCClient`. /// /// - Parameters: /// - client: A `GRPCCore.GRPCClient` providing a communication channel to the service. - public init(wrapping client: GRPCCore.GRPCClient) { + public init(wrapping client: GRPCCore.GRPCClient) { self.client = client } diff --git a/Tests/GRPCCodeGenTests/Internal/Translator/IDLToStructuredSwiftTranslatorSnippetBasedTests.swift b/Tests/GRPCCodeGenTests/Internal/Translator/IDLToStructuredSwiftTranslatorSnippetBasedTests.swift index 8f4509b50..e765f52fe 100644 --- a/Tests/GRPCCodeGenTests/Internal/Translator/IDLToStructuredSwiftTranslatorSnippetBasedTests.swift +++ b/Tests/GRPCCodeGenTests/Internal/Translator/IDLToStructuredSwiftTranslatorSnippetBasedTests.swift @@ -118,7 +118,7 @@ final class IDLToStructuredSwiftTranslatorSnippetBasedTests: XCTestCase { // Default implementation of 'registerMethods(with:)'. extension NamespaceA_ServiceA.StreamingServiceProtocol { - public func registerMethods(with router: inout GRPCCore.RPCRouter) {} + public func registerMethods(with router: inout GRPCCore.RPCRouter) where Transport: GRPCCore.ServerTransport {} } // Default implementation of streaming methods from 'StreamingServiceProtocol'. diff --git a/Tests/GRPCCodeGenTests/Internal/Translator/ServerCodeTranslatorSnippetBasedTests.swift b/Tests/GRPCCodeGenTests/Internal/Translator/ServerCodeTranslatorSnippetBasedTests.swift index 1b48ddadb..b0475ce50 100644 --- a/Tests/GRPCCodeGenTests/Internal/Translator/ServerCodeTranslatorSnippetBasedTests.swift +++ b/Tests/GRPCCodeGenTests/Internal/Translator/ServerCodeTranslatorSnippetBasedTests.swift @@ -144,7 +144,7 @@ final class ServerCodeTranslatorSnippetBasedTests { } // Default implementation of 'registerMethods(with:)'. extension NamespaceA_ServiceA.StreamingServiceProtocol { - public func registerMethods(with router: inout GRPCCore.RPCRouter) { + public func registerMethods(with router: inout GRPCCore.RPCRouter) where Transport: GRPCCore.ServerTransport { router.registerHandler( forMethod: NamespaceA_ServiceA.Method.Unary.descriptor, deserializer: GRPCProtobuf.ProtobufDeserializer(), diff --git a/Tests/GRPCCoreTests/Call/Client/Internal/ClientRPCExecutorTestSupport/ClientRPCExecutorTestHarness+ServerBehavior.swift b/Tests/GRPCCoreTests/Call/Client/Internal/ClientRPCExecutorTestSupport/ClientRPCExecutorTestHarness+ServerBehavior.swift index 4a6e17ed2..2b0f2b90f 100644 --- a/Tests/GRPCCoreTests/Call/Client/Internal/ClientRPCExecutorTestSupport/ClientRPCExecutorTestHarness+ServerBehavior.swift +++ b/Tests/GRPCCoreTests/Call/Client/Internal/ClientRPCExecutorTestSupport/ClientRPCExecutorTestHarness+ServerBehavior.swift @@ -23,16 +23,16 @@ extension ClientRPCExecutorTestHarness { private let handler: @Sendable ( _ stream: RPCStream< - RPCAsyncSequence, - RPCWriter.Closable + RPCAsyncSequence, any Error>, + RPCWriter>.Closable > ) async throws -> Void init( _ handler: @escaping @Sendable ( RPCStream< - RPCAsyncSequence, - RPCWriter.Closable + RPCAsyncSequence, any Error>, + RPCWriter>.Closable > ) async throws -> Void ) { @@ -43,9 +43,9 @@ extension ClientRPCExecutorTestHarness { stream: RPCStream ) async throws where - Inbound.Element == RPCRequestPart, + Inbound.Element == RPCRequestPart<[UInt8]>, Inbound.Failure == any Error, - Outbound.Element == RPCResponsePart + Outbound.Element == RPCResponsePart<[UInt8]> { let erased = RPCStream( descriptor: stream.descriptor, @@ -61,7 +61,7 @@ extension ClientRPCExecutorTestHarness { extension ClientRPCExecutorTestHarness.ServerStreamHandler { static var echo: Self { return Self { stream in - let response = stream.inbound.map { part -> RPCResponsePart in + let response = stream.inbound.map { part -> RPCResponsePart<[UInt8]> in switch part { case .metadata(let metadata): return .metadata(metadata) diff --git a/Tests/GRPCCoreTests/Call/Server/Internal/ServerRPCExecutorTestSupport/ServerRPCExecutorTestHarness.swift b/Tests/GRPCCoreTests/Call/Server/Internal/ServerRPCExecutorTestSupport/ServerRPCExecutorTestHarness.swift index 6634d9e9f..954a2476e 100644 --- a/Tests/GRPCCoreTests/Call/Server/Internal/ServerRPCExecutorTestSupport/ServerRPCExecutorTestHarness.swift +++ b/Tests/GRPCCoreTests/Call/Server/Internal/ServerRPCExecutorTestSupport/ServerRPCExecutorTestHarness.swift @@ -52,7 +52,8 @@ struct ServerRPCExecutorTestHarness { self.interceptors = interceptors } - func execute( + func execute( + bytes: Bytes.Type = Bytes.self, deserializer: some MessageDeserializer, serializer: some MessageSerializer, handler: @escaping @Sendable ( @@ -60,10 +61,10 @@ struct ServerRPCExecutorTestHarness { ServerContext ) async throws -> StreamingServerResponse, producer: @escaping @Sendable ( - RPCWriter.Closable + RPCWriter>.Closable ) async throws -> Void, consumer: @escaping @Sendable ( - RPCAsyncSequence + RPCAsyncSequence, any Error> ) async throws -> Void ) async throws { try await self.execute( @@ -75,19 +76,19 @@ struct ServerRPCExecutorTestHarness { ) } - func execute( + func execute( deserializer: some MessageDeserializer, serializer: some MessageSerializer, handler: ServerHandler, producer: @escaping @Sendable ( - RPCWriter.Closable + RPCWriter>.Closable ) async throws -> Void, consumer: @escaping @Sendable ( - RPCAsyncSequence + RPCAsyncSequence, any Error> ) async throws -> Void ) async throws { - let input = GRPCAsyncThrowingStream.makeStream(of: RPCRequestPart.self) - let output = GRPCAsyncThrowingStream.makeStream(of: RPCResponsePart.self) + let input = GRPCAsyncThrowingStream.makeStream(of: RPCRequestPart.self) + let output = GRPCAsyncThrowingStream.makeStream(of: RPCResponsePart.self) try await withThrowingTaskGroup(of: Void.self) { group in group.addTask { @@ -130,10 +131,10 @@ struct ServerRPCExecutorTestHarness { func execute( handler: ServerHandler<[UInt8], [UInt8]> = .echo, producer: @escaping @Sendable ( - RPCWriter.Closable + RPCWriter>.Closable ) async throws -> Void, consumer: @escaping @Sendable ( - RPCAsyncSequence + RPCAsyncSequence, any Error> ) async throws -> Void ) async throws { try await self.execute( diff --git a/Tests/GRPCCoreTests/Call/Server/Internal/ServerRPCExecutorTests.swift b/Tests/GRPCCoreTests/Call/Server/Internal/ServerRPCExecutorTests.swift index b807d02cd..09982b20d 100644 --- a/Tests/GRPCCoreTests/Call/Server/Internal/ServerRPCExecutorTests.swift +++ b/Tests/GRPCCoreTests/Call/Server/Internal/ServerRPCExecutorTests.swift @@ -81,6 +81,7 @@ final class ServerRPCExecutorTests: XCTestCase { func testEchoSingleJSONMessage() async throws { let harness = ServerRPCExecutorTestHarness() try await harness.execute( + bytes: [UInt8].self, deserializer: JSONDeserializer(), serializer: JSONSerializer() ) { request, _ in @@ -110,6 +111,7 @@ final class ServerRPCExecutorTests: XCTestCase { func testEchoMultipleJSONMessages() async throws { let harness = ServerRPCExecutorTestHarness() try await harness.execute( + bytes: [UInt8].self, deserializer: JSONDeserializer(), serializer: JSONSerializer() ) { request, _ in @@ -142,6 +144,7 @@ final class ServerRPCExecutorTests: XCTestCase { func testReturnTrailingMetadata() async throws { let harness = ServerRPCExecutorTestHarness() try await harness.execute( + bytes: [UInt8].self, deserializer: IdentityDeserializer(), serializer: IdentitySerializer() ) { request, _ in @@ -233,6 +236,7 @@ final class ServerRPCExecutorTests: XCTestCase { func testHandlerRespectsTimeout() async throws { let harness = ServerRPCExecutorTestHarness() try await harness.execute( + bytes: [UInt8].self, deserializer: IdentityDeserializer(), serializer: IdentitySerializer() ) { request, context in @@ -260,6 +264,7 @@ final class ServerRPCExecutorTests: XCTestCase { // The interceptor skips the handler altogether. let harness = ServerRPCExecutorTestHarness(interceptors: [.rejectAll(with: error)]) try await harness.execute( + bytes: [UInt8].self, deserializer: IdentityDeserializer(), serializer: IdentitySerializer() ) { request, _ in diff --git a/Tests/GRPCCoreTests/Call/Server/RPCRouterTests.swift b/Tests/GRPCCoreTests/Call/Server/RPCRouterTests.swift index 36d8e5580..7ae55b2da 100644 --- a/Tests/GRPCCoreTests/Call/Server/RPCRouterTests.swift +++ b/Tests/GRPCCoreTests/Call/Server/RPCRouterTests.swift @@ -19,7 +19,7 @@ import XCTest final class RPCRouterTests: XCTestCase { func testEmptyRouter() async throws { - var router = RPCRouter() + var router = RPCRouter() XCTAssertEqual(router.count, 0) XCTAssertEqual(router.methods, []) XCTAssertFalse( @@ -31,7 +31,7 @@ final class RPCRouterTests: XCTestCase { } func testRegisterMethod() async throws { - var router = RPCRouter() + var router = RPCRouter() let method = MethodDescriptor(fullyQualifiedService: "foo", method: "bar") router.registerHandler( forMethod: method, @@ -47,7 +47,7 @@ final class RPCRouterTests: XCTestCase { } func testRemoveMethod() async throws { - var router = RPCRouter() + var router = RPCRouter() let method = MethodDescriptor(fullyQualifiedService: "foo", method: "bar") router.registerHandler( forMethod: method, @@ -63,3 +63,18 @@ final class RPCRouterTests: XCTestCase { XCTAssertEqual(router.methods, []) } } + +struct NoServerTransport: ServerTransport { + typealias Bytes = [UInt8] + + func listen( + streamHandler: @escaping @Sendable ( + GRPCCore.RPCStream, + GRPCCore.ServerContext + ) async -> Void + ) async throws { + } + + func beginGracefulShutdown() { + } +} diff --git a/Tests/GRPCCoreTests/Coding/CodingTests.swift b/Tests/GRPCCoreTests/Coding/CodingTests.swift index efb57f94f..dab5ecece 100644 --- a/Tests/GRPCCoreTests/Coding/CodingTests.swift +++ b/Tests/GRPCCoreTests/Coding/CodingTests.swift @@ -35,7 +35,7 @@ final class CodingTests: XCTestCase { let serializer = JSONSerializer() let deserializer = JSONDeserializer() - let bytes = try serializer.serialize(message) + let bytes = try serializer.serialize(message) as [UInt8] let roundTrip = try deserializer.deserialize(bytes) XCTAssertEqual(roundTrip, message) } diff --git a/Tests/GRPCCoreTests/GRPCClientTests.swift b/Tests/GRPCCoreTests/GRPCClientTests.swift index 0152a9c81..09a4e29ba 100644 --- a/Tests/GRPCCoreTests/GRPCClientTests.swift +++ b/Tests/GRPCCoreTests/GRPCClientTests.swift @@ -23,7 +23,10 @@ final class GRPCClientTests: XCTestCase { func withInProcessConnectedClient( services: [any RegistrableRPCService], interceptorPipeline: [ClientInterceptorPipelineOperation] = [], - _ body: (GRPCClient, GRPCServer) async throws -> Void + _ body: ( + GRPCClient, + GRPCServer + ) async throws -> Void ) async throws { let inProcess = InProcessTransport() _ = GRPCClient(transport: inProcess.client, interceptorPipeline: interceptorPipeline) @@ -43,22 +46,6 @@ final class GRPCClientTests: XCTestCase { } } - 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( @@ -539,7 +526,10 @@ struct ClientTests { func withInProcessConnectedClient( services: [any RegistrableRPCService], interceptorPipeline: [ClientInterceptorPipelineOperation] = [], - _ body: (GRPCClient, GRPCServer) async throws -> Void + _ body: ( + GRPCClient, + GRPCServer + ) async throws -> Void ) async throws { let inProcess = InProcessTransport() let client = GRPCClient(transport: inProcess.client, interceptorPipeline: interceptorPipeline) diff --git a/Tests/GRPCCoreTests/GRPCServerTests.swift b/Tests/GRPCCoreTests/GRPCServerTests.swift index 388940e83..17e8126ff 100644 --- a/Tests/GRPCCoreTests/GRPCServerTests.swift +++ b/Tests/GRPCCoreTests/GRPCServerTests.swift @@ -23,7 +23,7 @@ final class GRPCServerTests: XCTestCase { func withInProcessClientConnectedToServer( services: [any RegistrableRPCService], interceptorPipeline: [ServerInterceptorPipelineOperation] = [], - _ body: (InProcessTransport.Client, GRPCServer) async throws -> Void + _ body: (InProcessTransport.Client, GRPCServer) async throws -> Void ) async throws { let inProcess = InProcessTransport() @@ -360,7 +360,7 @@ final class GRPCServerTests: XCTestCase { } } - private func doEchoGet(using transport: some ClientTransport) async throws { + private func doEchoGet(using transport: some ClientTransport<[UInt8]>) async throws { try await transport.withStream( descriptor: BinaryEcho.Methods.get, options: .defaults @@ -554,7 +554,7 @@ struct ServerTests { func withInProcessClientConnectedToServer( services: [any RegistrableRPCService], interceptorPipeline: [ServerInterceptorPipelineOperation] = [], - _ body: (InProcessTransport.Client, GRPCServer) async throws -> Void + _ body: (InProcessTransport.Client, GRPCServer) async throws -> Void ) async throws { let inProcess = InProcessTransport() let server = GRPCServer( @@ -578,8 +578,8 @@ struct ServerTests { } } - func assertMetadata( - _ part: RPCResponsePart?, + func assertMetadata( + _ part: RPCResponsePart?, metadataHandler: (Metadata) -> Void = { _ in } ) { switch part { @@ -590,9 +590,9 @@ struct ServerTests { } } - func assertMessage( - _ part: RPCResponsePart?, - messageHandler: ([UInt8]) -> Void = { _ in } + func assertMessage( + _ part: RPCResponsePart?, + messageHandler: (Bytes) -> Void = { _ in } ) { switch part { case .some(.message(let message)): @@ -602,8 +602,8 @@ struct ServerTests { } } - func assertStatus( - _ part: RPCResponsePart?, + func assertStatus( + _ part: RPCResponsePart?, statusHandler: (Status, Metadata) -> Void = { _, _ in } ) { switch part { diff --git a/Tests/GRPCCoreTests/RPCPartsTests.swift b/Tests/GRPCCoreTests/RPCPartsTests.swift index e950a8e97..3bf72e85e 100644 --- a/Tests/GRPCCoreTests/RPCPartsTests.swift +++ b/Tests/GRPCCoreTests/RPCPartsTests.swift @@ -18,7 +18,7 @@ import XCTest final class RPCPartsTests: XCTestCase { func testPartsFitInExistentialContainer() { - XCTAssertLessThanOrEqual(MemoryLayout.size, 24) - XCTAssertLessThanOrEqual(MemoryLayout.size, 24) + XCTAssertLessThanOrEqual(MemoryLayout>.size, 24) + XCTAssertLessThanOrEqual(MemoryLayout>.size, 24) } } diff --git a/Tests/GRPCCoreTests/Test Utilities/Coding+Identity.swift b/Tests/GRPCCoreTests/Test Utilities/Coding+Identity.swift index 335426fad..35eca49c8 100644 --- a/Tests/GRPCCoreTests/Test Utilities/Coding+Identity.swift +++ b/Tests/GRPCCoreTests/Test Utilities/Coding+Identity.swift @@ -16,13 +16,15 @@ import GRPCCore struct IdentitySerializer: MessageSerializer { - func serialize(_ message: [UInt8]) throws -> [UInt8] { - return message + func serialize(_ message: [UInt8]) throws -> Bytes { + return Bytes(message) } } struct IdentityDeserializer: MessageDeserializer { - func deserialize(_ serializedMessageBytes: [UInt8]) throws -> [UInt8] { - return serializedMessageBytes + func deserialize(_ serializedMessageBytes: Bytes) throws -> [UInt8] { + return serializedMessageBytes.withUnsafeBytes { + Array($0) + } } } diff --git a/Tests/GRPCCoreTests/Test Utilities/Coding+JSON.swift b/Tests/GRPCCoreTests/Test Utilities/Coding+JSON.swift index 3008cf3ef..3eb025783 100644 --- a/Tests/GRPCCoreTests/Test Utilities/Coding+JSON.swift +++ b/Tests/GRPCCoreTests/Test Utilities/Coding+JSON.swift @@ -20,10 +20,11 @@ import class Foundation.JSONDecoder import class Foundation.JSONEncoder struct JSONSerializer: MessageSerializer { - func serialize(_ message: Message) throws -> [UInt8] { + func serialize(_ message: Message) throws -> Bytes { do { let jsonEncoder = JSONEncoder() - return try Array(jsonEncoder.encode(message)) + let data = try jsonEncoder.encode(message) + return Bytes(data) } catch { throw RPCError(code: .internalError, message: "Can't serialize message to JSON. \(error)") } @@ -31,10 +32,11 @@ struct JSONSerializer: MessageSerializer { } struct JSONDeserializer: MessageDeserializer { - func deserialize(_ serializedMessageBytes: [UInt8]) throws -> Message { + func deserialize(_ serializedMessageBytes: Bytes) throws -> Message { do { let jsonDecoder = JSONDecoder() - return try jsonDecoder.decode(Message.self, from: Data(serializedMessageBytes)) + let data = serializedMessageBytes.withUnsafeBytes { Data($0) } + return try jsonDecoder.decode(Message.self, from: data) } catch { throw RPCError(code: .internalError, message: "Can't deserialze message from JSON. \(error)") } diff --git a/Tests/GRPCCoreTests/Test Utilities/Services/BinaryEcho.swift b/Tests/GRPCCoreTests/Test Utilities/Services/BinaryEcho.swift index a0043eda3..4783d03e2 100644 --- a/Tests/GRPCCoreTests/Test Utilities/Services/BinaryEcho.swift +++ b/Tests/GRPCCoreTests/Test Utilities/Services/BinaryEcho.swift @@ -53,7 +53,7 @@ struct BinaryEcho: RegistrableRPCService { } } - func registerMethods(with router: inout RPCRouter) { + func registerMethods(with router: inout RPCRouter) { let serializer = IdentitySerializer() let deserializer = IdentityDeserializer() diff --git a/Tests/GRPCCoreTests/Test Utilities/Services/HelloWorld.swift b/Tests/GRPCCoreTests/Test Utilities/Services/HelloWorld.swift index 253fbca1c..a543defc8 100644 --- a/Tests/GRPCCoreTests/Test Utilities/Services/HelloWorld.swift +++ b/Tests/GRPCCoreTests/Test Utilities/Services/HelloWorld.swift @@ -26,7 +26,7 @@ struct HelloWorld: RegistrableRPCService { return ServerResponse(message: Array("Hello, \(name)!".utf8), metadata: []) } - func registerMethods(with router: inout RPCRouter) { + func registerMethods(with router: inout RPCRouter) { let serializer = IdentitySerializer() let deserializer = IdentityDeserializer() diff --git a/Tests/GRPCCoreTests/Test Utilities/Transport/AnyTransport.swift b/Tests/GRPCCoreTests/Test Utilities/Transport/AnyTransport.swift index 7ca178fef..0678cd52b 100644 --- a/Tests/GRPCCoreTests/Test Utilities/Transport/AnyTransport.swift +++ b/Tests/GRPCCoreTests/Test Utilities/Transport/AnyTransport.swift @@ -16,8 +16,7 @@ @testable import GRPCCore struct AnyClientTransport: ClientTransport, Sendable { - typealias Inbound = RPCAsyncSequence - typealias Outbound = RPCWriter.Closable + typealias Bytes = [UInt8] private let _retryThrottle: @Sendable () -> RetryThrottle? private let _withStream: @@ -30,8 +29,7 @@ struct AnyClientTransport: ClientTransport, Sendable { private let _close: @Sendable () -> Void private let _configuration: @Sendable (MethodDescriptor) -> MethodConfig? - init(wrapping transport: Transport) - where Transport.Inbound == Inbound, Transport.Outbound == Outbound { + init(wrapping transport: Transport) where Transport.Bytes == [UInt8] { self._retryThrottle = { transport.retryThrottle } self._withStream = { descriptor, options, closure in try await transport.withStream(descriptor: descriptor, options: options) { stream in @@ -81,8 +79,7 @@ struct AnyClientTransport: ClientTransport, Sendable { } struct AnyServerTransport: ServerTransport, Sendable { - typealias Inbound = RPCAsyncSequence - typealias Outbound = RPCWriter.Closable + typealias Bytes = [UInt8] private let _listen: @Sendable ( @@ -93,7 +90,7 @@ struct AnyServerTransport: ServerTransport, Sendable { ) async throws -> Void private let _stopListening: @Sendable () -> Void - init(wrapping transport: Transport) { + init(wrapping transport: Transport) where Transport.Bytes == [UInt8] { self._listen = { streamHandler in try await transport.listen(streamHandler: streamHandler) } self._stopListening = { transport.beginGracefulShutdown() } } diff --git a/Tests/GRPCCoreTests/Test Utilities/Transport/StreamCountingTransport.swift b/Tests/GRPCCoreTests/Test Utilities/Transport/StreamCountingTransport.swift index 970109286..350208615 100644 --- a/Tests/GRPCCoreTests/Test Utilities/Transport/StreamCountingTransport.swift +++ b/Tests/GRPCCoreTests/Test Utilities/Transport/StreamCountingTransport.swift @@ -17,8 +17,7 @@ @testable import GRPCCore struct StreamCountingClientTransport: ClientTransport, Sendable { - typealias Inbound = RPCAsyncSequence - typealias Outbound = RPCWriter.Closable + typealias Bytes = [UInt8] private let transport: AnyClientTransport private let _streamsOpened: AtomicCounter @@ -32,8 +31,7 @@ struct StreamCountingClientTransport: ClientTransport, Sendable { self._streamFailures.value } - init(wrapping transport: Transport) - where Transport.Inbound == Inbound, Transport.Outbound == Outbound { + init(wrapping transport: Transport) where Transport.Bytes == [UInt8] { self.transport = AnyClientTransport(wrapping: transport) self._streamsOpened = AtomicCounter() self._streamFailures = AtomicCounter() @@ -78,8 +76,7 @@ struct StreamCountingClientTransport: ClientTransport, Sendable { } struct StreamCountingServerTransport: ServerTransport, Sendable { - typealias Inbound = RPCAsyncSequence - typealias Outbound = RPCWriter.Closable + typealias Bytes = [UInt8] private let transport: AnyServerTransport private let _acceptedStreams: AtomicCounter @@ -88,7 +85,7 @@ struct StreamCountingServerTransport: ServerTransport, Sendable { self._acceptedStreams.value } - init(wrapping transport: Transport) { + init(wrapping transport: Transport) where Transport.Bytes == [UInt8] { self.transport = AnyServerTransport(wrapping: transport) self._acceptedStreams = AtomicCounter() } diff --git a/Tests/GRPCCoreTests/Test Utilities/Transport/ThrowingTransport.swift b/Tests/GRPCCoreTests/Test Utilities/Transport/ThrowingTransport.swift index e73bdbdf1..f0e433f9c 100644 --- a/Tests/GRPCCoreTests/Test Utilities/Transport/ThrowingTransport.swift +++ b/Tests/GRPCCoreTests/Test Utilities/Transport/ThrowingTransport.swift @@ -16,8 +16,7 @@ @testable import GRPCCore struct ThrowOnStreamCreationTransport: ClientTransport { - typealias Inbound = RPCAsyncSequence - typealias Outbound = RPCWriter.Closable + typealias Bytes = [UInt8] private let code: RPCError.Code @@ -51,6 +50,8 @@ struct ThrowOnStreamCreationTransport: ClientTransport { } struct ThrowOnRunServerTransport: ServerTransport { + typealias Bytes = [UInt8] + func listen( streamHandler: ( _ stream: RPCStream, @@ -69,6 +70,8 @@ struct ThrowOnRunServerTransport: ServerTransport { } struct ThrowOnSignalServerTransport: ServerTransport { + typealias Bytes = [UInt8] + let signal: AsyncStream init(signal: AsyncStream) { diff --git a/Tests/GRPCCoreTests/Test Utilities/XCTest+Utilities.swift b/Tests/GRPCCoreTests/Test Utilities/XCTest+Utilities.swift index cc50b14b9..07acf664e 100644 --- a/Tests/GRPCCoreTests/Test Utilities/XCTest+Utilities.swift +++ b/Tests/GRPCCoreTests/Test Utilities/XCTest+Utilities.swift @@ -116,8 +116,8 @@ func XCTAssertRejected( } } -func XCTAssertMetadata( - _ part: RPCResponsePart?, +func XCTAssertMetadata( + _ part: RPCResponsePart?, metadataHandler: (Metadata) -> Void = { _ in } ) { switch part { @@ -128,8 +128,8 @@ func XCTAssertMetadata( } } -func XCTAssertMetadata( - _ part: RPCRequestPart?, +func XCTAssertMetadata( + _ part: RPCRequestPart?, metadataHandler: (Metadata) async throws -> Void = { _ in } ) async throws { switch part { @@ -140,9 +140,9 @@ func XCTAssertMetadata( } } -func XCTAssertMessage( - _ part: RPCResponsePart?, - messageHandler: ([UInt8]) -> Void = { _ in } +func XCTAssertMessage( + _ part: RPCResponsePart?, + messageHandler: (Bytes) -> Void = { _ in } ) { switch part { case .some(.message(let message)): @@ -152,9 +152,9 @@ func XCTAssertMessage( } } -func XCTAssertMessage( - _ part: RPCRequestPart?, - messageHandler: ([UInt8]) async throws -> Void = { _ in } +func XCTAssertMessage( + _ part: RPCRequestPart?, + messageHandler: (Bytes) async throws -> Void = { _ in } ) async throws { switch part { case .some(.message(let message)): @@ -164,8 +164,8 @@ func XCTAssertMessage( } } -func XCTAssertStatus( - _ part: RPCResponsePart?, +func XCTAssertStatus( + _ part: RPCResponsePart?, statusHandler: (Status, Metadata) -> Void = { _, _ in } ) { switch part { diff --git a/Tests/GRPCInProcessTransportTests/InProcessServerTransportTests.swift b/Tests/GRPCInProcessTransportTests/InProcessServerTransportTests.swift index 487dfc953..92c3c5c8e 100644 --- a/Tests/GRPCInProcessTransportTests/InProcessServerTransportTests.swift +++ b/Tests/GRPCInProcessTransportTests/InProcessServerTransportTests.swift @@ -23,10 +23,10 @@ final class InProcessServerTransportTests: XCTestCase { func testStartListening() async throws { let transport = InProcessTransport.Server(peer: "in-process:1234") - let outbound = GRPCAsyncThrowingStream.makeStream(of: RPCResponsePart.self) + let outbound = GRPCAsyncThrowingStream.makeStream(of: RPCResponsePart<[UInt8]>.self) let stream = RPCStream< - RPCAsyncSequence, - RPCWriter.Closable + RPCAsyncSequence, any Error>, + RPCWriter>.Closable >( descriptor: .testTest, inbound: RPCAsyncSequence( @@ -55,9 +55,10 @@ final class InProcessServerTransportTests: XCTestCase { func testStopListening() async throws { let transport = InProcessTransport.Server(peer: "in-process:1234") - let firstStreamOutbound = GRPCAsyncThrowingStream.makeStream(of: RPCResponsePart.self) + let firstStreamOutbound = GRPCAsyncThrowingStream.makeStream(of: RPCResponsePart<[UInt8]>.self) let firstStream = RPCStream< - RPCAsyncSequence, RPCWriter.Closable + RPCAsyncSequence, any Error>, + RPCWriter>.Closable >( descriptor: .testTest, inbound: RPCAsyncSequence( @@ -79,9 +80,12 @@ final class InProcessServerTransportTests: XCTestCase { transport.beginGracefulShutdown() - let secondStreamOutbound = GRPCAsyncThrowingStream.makeStream(of: RPCResponsePart.self) + let secondStreamOutbound = GRPCAsyncThrowingStream.makeStream( + of: RPCResponsePart<[UInt8]>.self + ) let secondStream = RPCStream< - RPCAsyncSequence, RPCWriter.Closable + RPCAsyncSequence, any Error>, + RPCWriter>.Closable >( descriptor: .testTest, inbound: RPCAsyncSequence( diff --git a/Tests/GRPCInProcessTransportTests/InProcessTransportTests.swift b/Tests/GRPCInProcessTransportTests/InProcessTransportTests.swift index 786f6fe99..2a4650e03 100644 --- a/Tests/GRPCInProcessTransportTests/InProcessTransportTests.swift +++ b/Tests/GRPCInProcessTransportTests/InProcessTransportTests.swift @@ -23,7 +23,10 @@ struct InProcessTransportTests { private static let cancellationModes = ["await-cancelled", "with-cancellation-handler"] private func withTestServerAndClient( - execute: (GRPCServer, GRPCClient) async throws -> Void + execute: ( + GRPCServer, + GRPCClient + ) async throws -> Void ) async throws { try await withThrowingDiscardingTaskGroup { group in let inProcess = InProcessTransport() @@ -126,7 +129,7 @@ private struct TestService: RegistrableRPCService { return ServerResponse(message: context.peer) } - func registerMethods(with router: inout RPCRouter) { + func registerMethods(with router: inout RPCRouter) { router.registerHandler( forMethod: .testCancellation, deserializer: UTF8Deserializer(), @@ -164,24 +167,26 @@ extension MethodDescriptor { } private struct UTF8Serializer: MessageSerializer { - func serialize(_ message: String) throws -> [UInt8] { - Array(message.utf8) + func serialize(_ message: String) throws -> Bytes { + Bytes(message.utf8) } } private struct UTF8Deserializer: MessageDeserializer { - func deserialize(_ serializedMessageBytes: [UInt8]) throws -> String { - String(decoding: serializedMessageBytes, as: UTF8.self) + func deserialize(_ serializedMessageBytes: Bytes) throws -> String { + serializedMessageBytes.withUnsafeBytes { + String(decoding: $0, as: UTF8.self) + } } } private struct VoidSerializer: MessageSerializer { - func serialize(_ message: Void) throws -> [UInt8] { - [] + func serialize(_ message: Void) throws -> Bytes { + Bytes(repeating: 0, count: 0) } } private struct VoidDeserializer: MessageDeserializer { - func deserialize(_ serializedMessageBytes: [UInt8]) throws { + func deserialize(_ serializedMessageBytes: Bytes) throws { } } From 202ba35beb442efa2ca05411f43b9d1dc4f948ef Mon Sep 17 00:00:00 2001 From: George Barnett Date: Fri, 17 Jan 2025 10:20:24 +0000 Subject: [PATCH 2/3] fix up peer info --- .../InProcessTransportTests.swift | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Tests/GRPCInProcessTransportTests/InProcessTransportTests.swift b/Tests/GRPCInProcessTransportTests/InProcessTransportTests.swift index 1849d7ff2..f83b2c73c 100644 --- a/Tests/GRPCInProcessTransportTests/InProcessTransportTests.swift +++ b/Tests/GRPCInProcessTransportTests/InProcessTransportTests.swift @@ -17,6 +17,9 @@ import GRPCCore import GRPCInProcessTransport import Testing +import struct Foundation.Data +import class Foundation.JSONDecoder +import class Foundation.JSONEncoder @Suite("InProcess transport") struct InProcessTransportTests { @@ -172,14 +175,16 @@ private struct PeerInfo: Codable { } private struct PeerInfoSerializer: MessageSerializer { - func serialize(_ message: PeerInfo) throws -> [UInt8] { - Array("\(message.local) \(message.remote)".utf8) + func serialize(_ message: PeerInfo) throws -> Bytes { + Bytes("\(message.local) \(message.remote)".utf8) } } private struct PeerInfoDeserializer: MessageDeserializer { - func deserialize(_ serializedMessageBytes: [UInt8]) throws -> PeerInfo { - let stringPeerInfo = String(decoding: serializedMessageBytes, as: UTF8.self) + func deserialize(_ serializedMessageBytes: Bytes) throws -> PeerInfo { + let stringPeerInfo = serializedMessageBytes.withUnsafeBytes { + String(decoding: $0, as: UTF8.self) + } let peerInfoComponents = stringPeerInfo.split(separator: " ") return PeerInfo(local: String(peerInfoComponents[0]), remote: String(peerInfoComponents[1])) } From b95f787f8a0b9af35e71760f5e50f6f8b221ca67 Mon Sep 17 00:00:00 2001 From: George Barnett Date: Fri, 17 Jan 2025 10:36:53 +0000 Subject: [PATCH 3/3] format --- .../GRPCInProcessTransportTests/InProcessTransportTests.swift | 3 --- 1 file changed, 3 deletions(-) diff --git a/Tests/GRPCInProcessTransportTests/InProcessTransportTests.swift b/Tests/GRPCInProcessTransportTests/InProcessTransportTests.swift index f83b2c73c..1de0a12bd 100644 --- a/Tests/GRPCInProcessTransportTests/InProcessTransportTests.swift +++ b/Tests/GRPCInProcessTransportTests/InProcessTransportTests.swift @@ -17,9 +17,6 @@ import GRPCCore import GRPCInProcessTransport import Testing -import struct Foundation.Data -import class Foundation.JSONDecoder -import class Foundation.JSONEncoder @Suite("InProcess transport") struct InProcessTransportTests {