Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Swift: Wire initializer refactor #2561

Merged
merged 7 commits into from
Aug 17, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 23 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -551,7 +551,6 @@ Swift is a pragmatic and expressive programming language with rich support for v
Here's how we used Swift to model Protocol Buffers messages:

* Messages are structs that conform to `Equatable`, `Codable` and `Sendable`. All Messages have value semantics.
* Messages have a memberwise initializer to populate fields.
* Fields are generated as properties.
* The nullability of each field's type depends on its label: `required`, `repeated` and `map`
fields get non-nullable types, whereas `optional` fields are of nullable types.
Expand All @@ -576,12 +575,22 @@ public struct Dinosaur {
/**
* URLs with images of this dinosaur.
*/
public var picture_urls: [String]
public var picture_urls: [String] = []
public var length_meters: Double?
public var mass_kilograms: Double?
public var period: Period?
public var unknownFields: Data = .init()
public var unknownFields: Foundation.Data = .init()

public init(configure: (inout Self) -> Void = { _ in }) {
configure(&self)
}

}

#if WIRE_INCLUDE_MEMBERWISE_INITIALIZER
extension Dinosaur {

@_disfavoredOverload
public init(
name: String? = nil,
picture_urls: [String] = [],
Expand All @@ -597,6 +606,7 @@ public struct Dinosaur {
}

}
#endif

#if !WIRE_REMOVE_EQUATABLE
extension Dinosaur : Equatable {
Expand All @@ -614,12 +624,15 @@ extension Dinosaur : Sendable {
#endif

extension Dinosaur : ProtoMessage {

public static func protoMessageTypeURL() -> String {
return "type.googleapis.com/squareup.dinosaurs.Dinosaur"
}

}

extension Dinosaur : Proto2Codable {

public init(from reader: ProtoReader) throws {
var name: String? = nil
var picture_urls: [String] = []
Expand Down Expand Up @@ -655,10 +668,12 @@ extension Dinosaur : Proto2Codable {
try writer.encode(tag: 5, value: self.period)
try writer.writeUnknownFields(unknownFields)
}

}

#if !WIRE_REMOVE_CODABLE
extension Dinosaur : Codable {

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: StringLiteralCodingKeys.self)
self.name = try container.decodeIfPresent(String.self, forKey: "name")
Expand All @@ -681,17 +696,18 @@ extension Dinosaur : Codable {
try container.encodeIfPresent(self.mass_kilograms, forKey: preferCamelCase ? "massKilograms" : "mass_kilograms")
try container.encodeIfPresent(self.period, forKey: "period")
}

}
#endif
```

Creating and accessing proto models is easy:

```swift
let stegosaurus = Dinosaur(
name: "Stegosaurus",
period: .JURASSIC
)
let stegosaurus = Dinosaur {
$0.name = "Stegosaurus"
$0.period = .JURASSIC
}

print("My favorite dinosaur existed in the \(stegosaurus.period) period.")
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,14 @@ public struct Duration {
public var nanos: Int32
public var unknownFields: Foundation.Data = .init()

public init(seconds: Int64, nanos: Int32) {
public init(
seconds: Int64,
nanos: Int32,
configure: (inout Self) -> Void = { _ in }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there value in having the configure block if all of the fields are required? (Or if there are no fields, like the OneOfOptions below?)

) {
self.seconds = seconds
self.nanos = nanos
configure(&self)
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ public struct OneofOptions {

public var unknownFields: Foundation.Data = .init()

public init() {
public init(configure: (inout Self) -> Void = { _ in }) {
configure(&self)
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,14 @@ public struct Timestamp {
public var nanos: Int32
public var unknownFields: Foundation.Data = .init()

public init(seconds: Int64, nanos: Int32) {
public init(
seconds: Int64,
nanos: Int32,
configure: (inout Self) -> Void = { _ in }
) {
self.seconds = seconds
self.nanos = nanos
configure(&self)
}

}
Expand Down
109 changes: 57 additions & 52 deletions wire-runtime-swift/src/test/swift/CodableTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,20 +44,20 @@ extension CodableTests {
}
"""

let expected = SimpleOptional2(
opt_int32: 1,
opt_int64: 2,
opt_uint32: 3,
opt_uint64: 4,
opt_float: 5,
opt_double: 6,
opt_bytes: Foundation.Data(hexEncoded: "0123456"),
opt_string: "foo",
opt_enum: .UNKNOWN,
repeated_int32: [1, 2, 3],
repeated_string: ["foo", "bar", "baz"],
map_int32_string: [1: "foo", 2: "bar"]
)
let expected = SimpleOptional2 {
$0.opt_int32 = 1
$0.opt_int64 = 2
$0.opt_uint32 = 3
$0.opt_uint64 = 4
$0.opt_float = 5
$0.opt_double = 6
$0.opt_bytes = Foundation.Data(hexEncoded: "0123456")
$0.opt_string = "foo"
$0.opt_enum = .UNKNOWN
$0.repeated_int32 = [1, 2, 3]
$0.repeated_string = ["foo", "bar", "baz"]
$0.map_int32_string = [1: "foo", 2: "bar"]
}

try assertDecode(json: json, expected: expected)
}
Expand Down Expand Up @@ -92,11 +92,12 @@ extension CodableTests {
req_double: 6,
req_bytes: Foundation.Data(hexEncoded: "0123456")!,
req_string: "foo",
req_enum: .UNKNOWN,
repeated_int32: [1, 2, 3],
repeated_string: ["foo", "bar", "baz"],
map_int32_string: [1: "foo", 2: "bar"]
)
req_enum: .UNKNOWN
) {
$0.repeated_int32 = [1, 2, 3]
$0.repeated_string = ["foo", "bar", "baz"]
$0.map_int32_string = [1: "foo", 2: "bar"]
}

try assertDecode(json: json, expected: expected)
}
Expand All @@ -107,20 +108,20 @@ extension CodableTests {
extension CodableTests {
func testEncodeOptional() throws {
// Only include one value in maps until https://bugs.swift.org/browse/SR-13414 is fixed.
let proto = SimpleOptional2(
opt_int32: 1,
opt_int64: 2,
opt_uint32: 3,
opt_uint64: 4,
opt_float: 5,
opt_double: 6,
opt_bytes: Foundation.Data(hexEncoded: "0123456"),
opt_string: "foo",
opt_enum: .UNKNOWN,
repeated_int32: [1, 2, 3],
repeated_string: ["foo", "bar", "baz"],
map_int32_string: [1: "foo"]
)
let proto = SimpleOptional2 {
$0.opt_int32 = 1
$0.opt_int64 = 2
$0.opt_uint32 = 3
$0.opt_uint64 = 4
$0.opt_float = 5
$0.opt_double = 6
$0.opt_bytes = Foundation.Data(hexEncoded: "0123456")
$0.opt_string = "foo"
$0.opt_enum = .UNKNOWN
$0.repeated_int32 = [1, 2, 3]
$0.repeated_string = ["foo", "bar", "baz"]
$0.map_int32_string = [1: "foo"]
}

let expected = """
{
Expand Down Expand Up @@ -155,11 +156,12 @@ extension CodableTests {
req_double: 6,
req_bytes: Foundation.Data(hexEncoded: "0123456")!,
req_string: "foo",
req_enum: .UNKNOWN,
repeated_int32: [1, 2, 3],
repeated_string: ["foo", "bar", "baz"],
map_int32_string: [1: "foo"]
)
req_enum: .UNKNOWN
) {
$0.repeated_int32 = [1, 2, 3]
$0.repeated_string = ["foo", "bar", "baz"]
$0.map_int32_string = [1: "foo"]
}

let expected = """
{
Expand Down Expand Up @@ -244,12 +246,12 @@ extension CodableTests {
}
"""

let proto = SimpleOptional2(
opt_double: 6,
opt_enum: .A,
repeated_string: ["B"],
map_int32_string: [1 : "foo"]
)
let proto = SimpleOptional2 {
$0.opt_double = 6
$0.opt_enum = .A
$0.repeated_string = ["B"]
$0.map_int32_string = [1 : "foo"]
}

try assertEncode(proto: proto, expected: json)
}
Expand All @@ -269,13 +271,13 @@ extension CodableTests {
}
"""

let proto = SimpleOptional2(
opt_int64: 5,
opt_double: 6,
opt_enum: .A,
repeated_string: ["B"],
map_int32_string: [1 : "foo"]
)
let proto = SimpleOptional2 {
$0.opt_int64 = 5
$0.opt_double = 6
$0.opt_enum = .A
$0.repeated_string = ["B"]
$0.map_int32_string = [1 : "foo"]
}

try assertDecode(json: json, expected: proto)
}
Expand All @@ -285,7 +287,10 @@ extension CodableTests {

extension CodableTests {
func testHeapRoundtrip() throws {
let proto = SwiftStackOverflow(value3: "hello")
let proto = SwiftStackOverflow {
$0.value3 = "hello"
}

let json = """
{
"value3":"hello"
Expand Down
8 changes: 6 additions & 2 deletions wire-runtime-swift/src/test/swift/DefaultedTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,17 @@ final class DefaultedTests: XCTestCase {
}

func testProjectedValueIsWrappedValue() throws {
let phoneNumber = Person.PhoneNumber(number: "1234567890", type: Person.PhoneType.WORK)
let phoneNumber = Person.PhoneNumber(number: "1234567890") {
$0.type = .WORK
}
XCTAssertEqual(phoneNumber.type, Person.PhoneType.WORK)
XCTAssertEqual(phoneNumber.$type, Person.PhoneType.WORK)
}

func testProjectedValueWhenResettingWrappedValue() throws {
var phoneNumber = Person.PhoneNumber(number: "1234567890", type: Person.PhoneType.WORK)
var phoneNumber = Person.PhoneNumber(number: "1234567890") {
$0.type = .WORK
}
XCTAssertEqual(phoneNumber.type, Person.PhoneType.WORK)
XCTAssertEqual(phoneNumber.$type, Person.PhoneType.WORK)
phoneNumber.type = nil
Expand Down
14 changes: 11 additions & 3 deletions wire-runtime-swift/src/test/swift/ProtoReaderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,10 @@ final class ProtoReaderTests: XCTestCase {
00 // Length 0
""")!
try test(data: data) { reader in
let expected = Parent(name: "Bob", child: .init())
let expected = Parent {
$0.name = "Bob"
$0.child = .init()
}
XCTAssertEqual(try reader.decode(Parent.self), expected)
}
}
Expand Down Expand Up @@ -327,7 +330,10 @@ final class ProtoReaderTests: XCTestCase {
01 // Value 1
""")!
try test(data: data, enumStrategy: .returnNil) { reader in
let message = OneOfs(standalone_enum: OneOfs.NestedEnum.A, choice: OneOfs.Choice.similar_enum_option(OneOfs.NestedEnum.A))
let message = OneOfs {
$0.standalone_enum = .A
$0.choice = .similar_enum_option(.A)
}
XCTAssertEqual(try reader.decode(OneOfs.self), message)
}
}
Expand All @@ -340,7 +346,9 @@ final class ProtoReaderTests: XCTestCase {
02 // Value 2
""")!
try test(data: data, enumStrategy: .returnNil) { reader in
let message = OneOfs(standalone_enum: OneOfs.NestedEnum.A, choice: nil)
let message = OneOfs {
$0.standalone_enum = .A
}
XCTAssertEqual(try reader.decode(OneOfs.self), message)
}
}
Expand Down
Loading