Browse Source

Adopt new serializer protocols (#29)

Motivation:

The core package made a few changes allowing for transport to define an
associated bag-of-bytes types so that they can avoid copying to and from
`[UInt8]`. This also came with changes to the serialization protocols.

Modifications:

- Add a thin adapter type to bridge between gRPC and Protobuf contiguous
bytes
- Adopt new protocols

Result:

Builds again
George Barnett 10 months ago
parent
commit
768985a79a

+ 10 - 5
Sources/GRPCProtobuf/Coding.swift

@@ -25,9 +25,11 @@ public struct ProtobufSerializer<Message: SwiftProtobuf.Message>: GRPCCore.Messa
   ///
   /// - Parameter message: The message to serialize.
   /// - Returns: An array of serialized bytes representing the message.
-  public func serialize(_ message: Message) throws -> [UInt8] {
+  @inlinable
+  public func serialize<Bytes: GRPCContiguousBytes>(_ message: Message) throws -> Bytes {
     do {
-      return try message.serializedBytes()
+      let adapter = try message.serializedBytes() as ContiguousBytesAdapter<Bytes>
+      return adapter.bytes
     } catch let error {
       throw RPCError(
         code: .invalidArgument,
@@ -46,14 +48,17 @@ public struct ProtobufDeserializer<Message: SwiftProtobuf.Message>: GRPCCore.Mes
   ///
   /// - Parameter serializedMessageBytes: The array of bytes to deserialize.
   /// - Returns: The deserialized message.
-  public func deserialize(_ serializedMessageBytes: [UInt8]) throws -> Message {
+  @inlinable
+  public func deserialize<Bytes: GRPCContiguousBytes>(
+    _ serializedMessageBytes: Bytes
+  ) throws -> Message {
     do {
-      let message = try Message(serializedBytes: serializedMessageBytes)
+      let message = try Message(serializedBytes: ContiguousBytesAdapter(serializedMessageBytes))
       return message
     } catch let error {
       throw RPCError(
         code: .invalidArgument,
-        message: "Can't deserialize to message of type \(Message.self)",
+        message: "Can't deserialize to message of type \(Message.self).",
         cause: error
       )
     }

+ 64 - 0
Sources/GRPCProtobuf/ContiguousBytesAdapter.swift

@@ -0,0 +1,64 @@
+/*
+ * 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.
+ */
+
+public import GRPCCore  // internal but @usableFromInline
+public import SwiftProtobuf  // internal but @usableFromInline
+
+/// Brides between `GRPCContiguousBytes` and `SwiftProtobufContiguousBytes` which have the same
+/// requirements.
+///
+/// This is necessary as `SwiftProtobufContiguousBytes` can't be the protocol in the gRPC API (as
+/// it'd require a dependency on Protobuf in the core package), and `GRPCContiguousBytes` can't
+/// refine `SwiftProtobufContiguousBytes` for the same reason.
+@usableFromInline
+struct ContiguousBytesAdapter<
+  Bytes: GRPCContiguousBytes
+>: GRPCContiguousBytes, SwiftProtobufContiguousBytes {
+  @usableFromInline
+  var bytes: Bytes
+
+  @inlinable
+  init(_ bytes: Bytes) {
+    self.bytes = bytes
+  }
+
+  @inlinable
+  init(repeating: UInt8, count: Int) {
+    self.bytes = Bytes(repeating: repeating, count: count)
+  }
+
+  @inlinable
+  init(_ sequence: some Sequence<UInt8>) {
+    self.bytes = Bytes(sequence)
+  }
+
+  @inlinable
+  var count: Int {
+    self.bytes.count
+  }
+
+  @inlinable
+  func withUnsafeBytes<R>(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R {
+    try self.bytes.withUnsafeBytes(body)
+  }
+
+  @inlinable
+  mutating func withUnsafeMutableBytes<R>(
+    _ body: (UnsafeMutableRawBufferPointer) throws -> R
+  ) rethrows -> R {
+    try self.bytes.withUnsafeMutableBytes(body)
+  }
+}

+ 4 - 4
Tests/GRPCProtobufCodeGenTests/ProtobufCodeGeneratorTests.swift

@@ -405,7 +405,7 @@ struct ProtobufCodeGeneratorTests {
 
         // Default implementation of 'registerMethods(with:)'.
         extension Test_TestService.StreamingServiceProtocol {
-          \(access) func registerMethods(with router: inout GRPCCore.RPCRouter) {
+          \(access) func registerMethods<Transport>(with router: inout GRPCCore.RPCRouter<Transport>) where Transport: GRPCCore.ServerTransport {
             router.registerHandler(
               forMethod: Test_TestService.Method.Unary.descriptor,
               deserializer: GRPCProtobuf.ProtobufDeserializer<Test_TestInput>(),
@@ -666,14 +666,14 @@ struct ProtobufCodeGeneratorTests {
           /// > Source IDL Documentation:
           /// >
           /// > Service docs.
-          \(access) struct Client: ClientProtocol {
-            private let client: GRPCCore.GRPCClient
+          \(access) struct Client<Transport>: ClientProtocol where Transport: GRPCCore.ClientTransport {
+            private let client: GRPCCore.GRPCClient<Transport>
 
             /// 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<Transport>) {
               self.client = client
             }
 

+ 4 - 4
Tests/GRPCProtobufTests/Errors/Generated/error-service.grpc.swift

@@ -134,7 +134,7 @@ extension ErrorService {
 
 // Default implementation of 'registerMethods(with:)'.
 extension ErrorService.StreamingServiceProtocol {
-    internal func registerMethods(with router: inout GRPCCore.RPCRouter) {
+    internal func registerMethods<Transport>(with router: inout GRPCCore.RPCRouter<Transport>) where Transport: GRPCCore.ServerTransport {
         router.registerHandler(
             forMethod: ErrorService.Method.ThrowError.descriptor,
             deserializer: GRPCProtobuf.ProtobufDeserializer<ThrowInput>(),
@@ -212,14 +212,14 @@ extension ErrorService {
     /// The ``Client`` provides an implementation of ``ClientProtocol`` which wraps
     /// a `GRPCCore.GRPCCClient`. The underlying `GRPCClient` provides the long-lived
     /// means of communication with the remote peer.
-    internal struct Client: ClientProtocol {
-        private let client: GRPCCore.GRPCClient
+    internal struct Client<Transport>: ClientProtocol where Transport: GRPCCore.ClientTransport {
+        private let client: GRPCCore.GRPCClient<Transport>
 
         /// Creates a new client wrapping the provided `GRPCCore.GRPCClient`.
         ///
         /// - Parameters:
         ///   - client: A `GRPCCore.GRPCClient` providing a communication channel to the service.
-        internal init(wrapping client: GRPCCore.GRPCClient) {
+        internal init(wrapping client: GRPCCore.GRPCClient<Transport>) {
             self.client = client
         }
 

+ 3 - 3
Tests/GRPCProtobufTests/ProtobufCodingTests.swift

@@ -28,7 +28,7 @@ final class ProtobufCodingTests: XCTestCase {
     let serializer = ProtobufSerializer<Google_Protobuf_Timestamp>()
     let deserializer = ProtobufDeserializer<Google_Protobuf_Timestamp>()
 
-    let bytes = try serializer.serialize(message)
+    let bytes = try serializer.serialize(message) as [UInt8]
     let roundTrip = try deserializer.deserialize(bytes)
     XCTAssertEqual(roundTrip, message)
   }
@@ -38,7 +38,7 @@ final class ProtobufCodingTests: XCTestCase {
     let serializer = ProtobufSerializer<TestMessage>()
 
     XCTAssertThrowsError(
-      try serializer.serialize(message)
+      try serializer.serialize(message) as [UInt8]
     ) { error in
       XCTAssertEqual(
         error as? RPCError,
@@ -65,7 +65,7 @@ final class ProtobufCodingTests: XCTestCase {
           code: .invalidArgument,
           message:
             """
-            Can't deserialize to message of type TestMessage
+            Can't deserialize to message of type TestMessage.
             """
         )
       )