Skip to content

Commit 1e36bdc

Browse files
[CodeGen Protobuf support] Message serializer and deserializer (#1764)
Motivation: In the generated code we are specifying types for a serializer and deserializer. As we want to support SwiftProtobuf, we need to implement the generic serializer and deserializer for Protobuf messages, which will be the default if the user doesn't specify other IDL serializer and deserializer. Modifications: Created the serializer and deserializer structs conforming to `GRPCCore.MessageSerializer` and `GRPCCOre.MessageDeserializer` respectively and implemented their serialize and deserialize functions. Added tests for them. Result: The generated code can reference protobuf serializer and deserializer types, as they will be supported in GRPC.
1 parent 783e469 commit 1e36bdc

File tree

3 files changed

+183
-1
lines changed

3 files changed

+183
-1
lines changed

Package.swift

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ extension Target.Dependency {
9393
static let protocGenGRPCSwift: Self = .target(name: "protoc-gen-grpc-swift")
9494
static let reflectionService: Self = .target(name: "GRPCReflectionService")
9595
static let grpcCodeGen: Self = .target(name: "GRPCCodeGen")
96+
static let grpcProtobuf: Self = .target(name: "GRPCProtobuf")
9697

9798
// Target dependencies; internal
9899
static let grpcSampleData: Self = .target(name: "GRPCSampleData")
@@ -330,6 +331,15 @@ extension Target {
330331
]
331332
)
332333

334+
static let grpcProtobufTests: Target = .testTarget(
335+
name: "GRPCProtobufTests",
336+
dependencies: [
337+
.grpcCore,
338+
.grpcProtobuf,
339+
.protobuf
340+
]
341+
)
342+
333343
static let interopTestModels: Target = .target(
334344
name: "GRPCInteroperabilityTestModels",
335345
dependencies: [
@@ -579,6 +589,15 @@ extension Target {
579589
name: "GRPCCodeGen",
580590
path: "Sources/GRPCCodeGen"
581591
)
592+
593+
static let grpcProtobuf: Target = .target(
594+
name: "GRPCProtobuf",
595+
dependencies: [
596+
.grpcCore,
597+
.protobuf
598+
],
599+
path: "Sources/GRPCProtobuf"
600+
)
582601
}
583602

584603
// MARK: - Products
@@ -666,6 +685,7 @@ let package = Package(
666685
.grpcHTTP2Core,
667686
.grpcHTTP2TransportNIOPosix,
668687
.grpcHTTP2TransportNIOTransportServices,
688+
.grpcProtobuf,
669689

670690
// v2 tests
671691
.grpcCoreTests,
@@ -674,7 +694,8 @@ let package = Package(
674694
.grpcInterceptorsTests,
675695
.grpcHTTP2CoreTests,
676696
.grpcHTTP2TransportNIOPosixTests,
677-
.grpcHTTP2TransportNIOTransportServicesTests
697+
.grpcHTTP2TransportNIOTransportServicesTests,
698+
.grpcProtobufTests
678699
]
679700
)
680701

Sources/GRPCProtobuf/Coding.swift

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright 2024, gRPC Authors All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import Foundation
18+
import GRPCCore
19+
import SwiftProtobuf
20+
21+
/// Serializes a Protobuf message into a sequence of bytes.
22+
public struct ProtobufSerializer<Message: SwiftProtobuf.Message>: GRPCCore.MessageSerializer {
23+
public init() {}
24+
25+
/// Serializes a ``Message`` into a sequence of bytes.
26+
///
27+
/// - Parameter message: The message to serialize.
28+
/// - Returns: An array of serialized bytes representing the message.
29+
public func serialize(_ message: Message) throws -> [UInt8] {
30+
do {
31+
let data = try message.serializedData()
32+
return Array(data)
33+
} catch let error {
34+
throw RPCError(
35+
code: .invalidArgument,
36+
message: "Can't serialize message of type \(type(of: message)).",
37+
cause: error
38+
)
39+
}
40+
}
41+
}
42+
43+
/// Deserializes a sequence of bytes into a Protobuf message.
44+
public struct ProtobufDeserializer<Message: SwiftProtobuf.Message>: GRPCCore.MessageDeserializer {
45+
public init() {}
46+
47+
/// Deserializes a sequence of bytes into a ``Message``.
48+
///
49+
/// - Parameter serializedMessageBytes: The array of bytes to deserialize.
50+
/// - Returns: The deserialized message.
51+
public func deserialize(_ serializedMessageBytes: [UInt8]) throws -> Message {
52+
do {
53+
let message = try Message(contiguousBytes: serializedMessageBytes)
54+
return message
55+
} catch let error {
56+
throw RPCError(
57+
code: .invalidArgument,
58+
message: "Can't deserialize to message of type \(Message.self)",
59+
cause: error
60+
)
61+
}
62+
}
63+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* Copyright 2024, gRPC Authors All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import GRPCCore
18+
import GRPCProtobuf
19+
import SwiftProtobuf
20+
import XCTest
21+
22+
final class ProtobufCodingTests: XCTestCase {
23+
func testSerializeDeserializeRoundtrip() throws {
24+
let message = Google_Protobuf_Timestamp.with {
25+
$0.seconds = 4
26+
}
27+
28+
let serializer = ProtobufSerializer<Google_Protobuf_Timestamp>()
29+
let deserializer = ProtobufDeserializer<Google_Protobuf_Timestamp>()
30+
31+
let bytes = try serializer.serialize(message)
32+
let roundTrip = try deserializer.deserialize(bytes)
33+
XCTAssertEqual(roundTrip, message)
34+
}
35+
36+
func testSerializerError() throws {
37+
let message = TestMessage()
38+
let serializer = ProtobufSerializer<TestMessage>()
39+
40+
XCTAssertThrowsError(
41+
try serializer.serialize(message)
42+
) { error in
43+
XCTAssertEqual(
44+
error as? RPCError,
45+
RPCError(
46+
code: .invalidArgument,
47+
message:
48+
"""
49+
Can't serialize message of type TestMessage.
50+
"""
51+
)
52+
)
53+
}
54+
}
55+
56+
func testDeserializerError() throws {
57+
let bytes = Array("%%%%%££££".utf8)
58+
let deserializer = ProtobufDeserializer<TestMessage>()
59+
XCTAssertThrowsError(
60+
try deserializer.deserialize(bytes)
61+
) { error in
62+
XCTAssertEqual(
63+
error as? RPCError,
64+
RPCError(
65+
code: .invalidArgument,
66+
message:
67+
"""
68+
Can't deserialize to message of type TestMessage
69+
"""
70+
)
71+
)
72+
}
73+
}
74+
}
75+
76+
struct TestMessage: SwiftProtobuf.Message {
77+
var text: String = ""
78+
var unknownFields = SwiftProtobuf.UnknownStorage()
79+
static var protoMessageName: String = "Test" + ".ServiceRequest"
80+
init() {}
81+
82+
mutating func decodeMessage<D>(decoder: inout D) throws where D: SwiftProtobuf.Decoder {
83+
throw RPCError(code: .internalError, message: "Decoding error")
84+
}
85+
86+
func traverse<V>(visitor: inout V) throws where V: SwiftProtobuf.Visitor {
87+
throw RPCError(code: .internalError, message: "Traversing error")
88+
}
89+
90+
public var isInitialized: Bool {
91+
if self.text.isEmpty { return false }
92+
return true
93+
}
94+
95+
func isEqualTo(message: SwiftProtobuf.Message) -> Bool {
96+
return false
97+
}
98+
}

0 commit comments

Comments
 (0)