Skip to content

Commit

Permalink
[swift] Provide a default value for sub fields and common types as pe…
Browse files Browse the repository at this point in the history
…r proto spec
  • Loading branch information
dnkoutso committed Oct 12, 2023
1 parent b9f6835 commit f2772d1
Show file tree
Hide file tree
Showing 24 changed files with 438 additions and 326 deletions.
86 changes: 45 additions & 41 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -571,17 +571,20 @@ public struct Dinosaur {
/**
* Common name of this dinosaur, like "Stegosaurus".
*/
@Defaulted(defaultValue: "")
public var name: String?
/**
* URLs with images of this dinosaur.
*/
public var picture_urls: [String] = []
@Defaulted(defaultValue: 0)
public var length_meters: Double?
@Defaulted(defaultValue: 0)
public var mass_kilograms: Double?
public var period: Period?
public var unknownFields: Foundation.Data = .init()

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

Expand All @@ -591,17 +594,18 @@ public struct Dinosaur {
extension Dinosaur {

@_disfavoredOverload
@available(*, deprecated)
public init(
name: String? = nil,
picture_urls: [String] = [],
length_meters: Double? = nil,
mass_kilograms: Double? = nil,
name: Swift.String? = nil,
picture_urls: [Swift.String] = [],
length_meters: Swift.Double? = nil,
mass_kilograms: Swift.Double? = nil,
period: Period? = nil
) {
self.name = name
self._name.wrappedValue = name
self.picture_urls = picture_urls
self.length_meters = length_meters
self.mass_kilograms = mass_kilograms
self._length_meters.wrappedValue = length_meters
self._mass_kilograms.wrappedValue = mass_kilograms
self.period = period
}

Expand All @@ -625,66 +629,66 @@ extension Dinosaur : Sendable {

extension Dinosaur : ProtoMessage {

public static func protoMessageTypeURL() -> String {
public static func protoMessageTypeURL() -> Swift.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] = []
var length_meters: Double? = nil
var mass_kilograms: Double? = nil
public init(from protoReader: Wire.ProtoReader) throws {
var name: Swift.String? = nil
var picture_urls: [Swift.String] = []
var length_meters: Swift.Double? = nil
var mass_kilograms: Swift.Double? = nil
var period: Period? = nil

let token = try reader.beginMessage()
while let tag = try reader.nextTag(token: token) {
let token = try protoReader.beginMessage()
while let tag = try protoReader.nextTag(token: token) {
switch tag {
case 1: name = try reader.decode(String.self)
case 2: try reader.decode(into: &picture_urls)
case 3: length_meters = try reader.decode(Double.self)
case 4: mass_kilograms = try reader.decode(Double.self)
case 5: period = try reader.decode(Period.self)
default: try reader.readUnknownField(tag: tag)
case 1: name = try protoReader.decode(Swift.String.self)
case 2: try protoReader.decode(into: &picture_urls)
case 3: length_meters = try protoReader.decode(Swift.Double.self)
case 4: mass_kilograms = try protoReader.decode(Swift.Double.self)
case 5: period = try protoReader.decode(Period.self)
default: try protoReader.readUnknownField(tag: tag)
}
}
self.unknownFields = try reader.endMessage(token: token)
self.unknownFields = try protoReader.endMessage(token: token)

self.name = name
self._name.wrappedValue = name
self.picture_urls = picture_urls
self.length_meters = length_meters
self.mass_kilograms = mass_kilograms
self._length_meters.wrappedValue = length_meters
self._mass_kilograms.wrappedValue = mass_kilograms
self.period = period
}

public func encode(to writer: ProtoWriter) throws {
try writer.encode(tag: 1, value: self.name)
try writer.encode(tag: 2, value: self.picture_urls)
try writer.encode(tag: 3, value: self.length_meters)
try writer.encode(tag: 4, value: self.mass_kilograms)
try writer.encode(tag: 5, value: self.period)
try writer.writeUnknownFields(unknownFields)
public func encode(to protoWriter: Wire.ProtoWriter) throws {
try protoWriter.encode(tag: 1, value: self.name)
try protoWriter.encode(tag: 2, value: self.picture_urls)
try protoWriter.encode(tag: 3, value: self.length_meters)
try protoWriter.encode(tag: 4, value: self.mass_kilograms)
try protoWriter.encode(tag: 5, value: self.period)
try protoWriter.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")
self.picture_urls = try container.decodeProtoArray(String.self, firstOfKeys: "pictureUrls", "picture_urls")
self.length_meters = try container.decodeIfPresent(Double.self, firstOfKeys: "lengthMeters", "length_meters")
self.mass_kilograms = try container.decodeIfPresent(Double.self, firstOfKeys: "massKilograms", "mass_kilograms")
public init(from decoder: Swift.Decoder) throws {
let container = try decoder.container(keyedBy: Wire.StringLiteralCodingKeys.self)
self._name.wrappedValue = try container.decodeIfPresent(Swift.String.self, forKey: "name")
self.picture_urls = try container.decodeProtoArray(Swift.String.self, firstOfKeys: "pictureUrls", "picture_urls")
self._length_meters.wrappedValue = try container.decodeIfPresent(Swift.Double.self, firstOfKeys: "lengthMeters", "length_meters")
self._mass_kilograms.wrappedValue = try container.decodeIfPresent(Swift.Double.self, firstOfKeys: "massKilograms", "mass_kilograms")
self.period = try container.decodeIfPresent(Period.self, forKey: "period")
}

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: StringLiteralCodingKeys.self)
public func encode(to encoder: Swift.Encoder) throws {
var container = encoder.container(keyedBy: Wire.StringLiteralCodingKeys.self)
let preferCamelCase = encoder.protoKeyNameEncodingStrategy == .camelCase
let includeDefaults = encoder.protoDefaultValuesEncodingStrategy == .include

Expand Down
21 changes: 12 additions & 9 deletions wire-runtime-swift/src/test/swift/sample/Dinosaur.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@ public struct Dinosaur {
/**
* Common name of this dinosaur, like "Stegosaurus".
*/
@Defaulted(defaultValue: "")
public var name: String?
/**
* URLs with images of this dinosaur.
*/
public var picture_urls: [String] = []
@Defaulted(defaultValue: 0)
public var length_meters: Double?
@Defaulted(defaultValue: 0)
public var mass_kilograms: Double?
public var period: Period?
public var unknownFields: Foundation.Data = .init()
Expand All @@ -36,10 +39,10 @@ extension Dinosaur {
mass_kilograms: Swift.Double? = nil,
period: Period? = nil
) {
self.name = name
self._name.wrappedValue = name
self.picture_urls = picture_urls
self.length_meters = length_meters
self.mass_kilograms = mass_kilograms
self._length_meters.wrappedValue = length_meters
self._mass_kilograms.wrappedValue = mass_kilograms
self.period = period
}

Expand Down Expand Up @@ -91,10 +94,10 @@ extension Dinosaur : Proto2Codable {
}
self.unknownFields = try protoReader.endMessage(token: token)

self.name = name
self._name.wrappedValue = name
self.picture_urls = picture_urls
self.length_meters = length_meters
self.mass_kilograms = mass_kilograms
self._length_meters.wrappedValue = length_meters
self._mass_kilograms.wrappedValue = mass_kilograms
self.period = period
}

Expand All @@ -114,10 +117,10 @@ extension Dinosaur : Codable {

public init(from decoder: Swift.Decoder) throws {
let container = try decoder.container(keyedBy: Wire.StringLiteralCodingKeys.self)
self.name = try container.decodeIfPresent(Swift.String.self, forKey: "name")
self._name.wrappedValue = try container.decodeIfPresent(Swift.String.self, forKey: "name")
self.picture_urls = try container.decodeProtoArray(Swift.String.self, firstOfKeys: "pictureUrls", "picture_urls")
self.length_meters = try container.decodeIfPresent(Swift.Double.self, firstOfKeys: "lengthMeters", "length_meters")
self.mass_kilograms = try container.decodeIfPresent(Swift.Double.self, firstOfKeys: "massKilograms", "mass_kilograms")
self._length_meters.wrappedValue = try container.decodeIfPresent(Swift.Double.self, firstOfKeys: "lengthMeters", "length_meters")
self._mass_kilograms.wrappedValue = try container.decodeIfPresent(Swift.Double.self, firstOfKeys: "massKilograms", "mass_kilograms")
self.period = try container.decodeIfPresent(Period.self, forKey: "period")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,40 @@ class SwiftGenerator private constructor(
else -> null
}

private val Field.defaultedValue: CodeBlock?
get() {
when (val default = default) {
// identity values
null -> {
if (!isOptional) return null
if (type == ProtoType.ANY) return null
if (isMessage && !isRequiredParameter && !isCollection) {
val messageType = schema.getType(type!!) as MessageType
return if (messageType.fields.any { it.isRequiredParameter }) null else CodeBlock.of("%T()", messageType.typeName.makeNonOptional())
}
if (isEnum) {
val enumType = schema.getType(type!!) as EnumType
return if (enumType.constants.filter { it.tag == 0 }.firstOrNull() != null) CodeBlock.of("%T.%L", typeName.makeNonOptional(), enumType.constants[0].name) else null
}
when (typeName.makeNonOptional()) {
BOOL -> return CodeBlock.of("%L", false)
INT -> return CodeBlock.of("%L", 0)
INT32 -> return CodeBlock.of("%L", 0)
INT64 -> return CodeBlock.of("%L", 0)
UINT32 -> return CodeBlock.of("%L", 0)
UINT64 -> return CodeBlock.of("%L", 0)
FLOAT -> return CodeBlock.of("%L", 0)
DOUBLE -> return CodeBlock.of("%L", 0)
STRING -> return CodeBlock.of("%S", "")
DATA -> return CodeBlock.of("%T()", DATA)
}
}
else -> return defaultFieldInitializer(type!!, default)
}

return null
}

// see https://protobuf.dev/programming-guides/proto3/#default
private val Field.proto3InitialValue: String
get() = when {
Expand Down Expand Up @@ -651,7 +685,7 @@ class SwiftGenerator private constructor(
}
}
addStatement(
if (field.default != null) "_%N.wrappedValue = %L" else { "self.%N = %L" },
if (!isIndirect(type, field) && field.defaultedValue != null) "self._%N.wrappedValue = %L" else { "self.%N = %L" },
field.name,
initializer,
)
Expand Down Expand Up @@ -831,7 +865,7 @@ class SwiftGenerator private constructor(
.map { CodeBlock.of("%S", it) }
.joinToCode()

val prefix = if (field.default != null) { "_%1N.wrappedValue" } else { "self.%1N" }
val prefix = if (!isIndirect(type, field) && field.defaultedValue != null) { "self._%1N.wrappedValue" } else { "self.%1N" }
addStatement(
"$prefix = try container.$decode($typeArg%2T.self, $forKeys: $keys)",
field.name,
Expand Down Expand Up @@ -1146,7 +1180,7 @@ class SwiftGenerator private constructor(
.apply {
type.fields.filter { it.isRequiredParameter }.forEach { field ->
addStatement(
if (field.default != null) "_%1N.wrappedValue = %1N" else { "self.%1N = %1N" },
if (!isIndirect(type, field) && field.defaultedValue != null) "self._%1N.wrappedValue = %1N" else { "self.%1N = %1N" },
field.name,
)
}
Expand All @@ -1172,7 +1206,7 @@ class SwiftGenerator private constructor(
.apply {
type.fields.forEach { field ->
addStatement(
if (field.default != null) "_%1N.wrappedValue = %1N" else { "self.%1N = %1N" },
if (!isIndirect(type, field) && field.defaultedValue != null) "self._%1N.wrappedValue = %1N" else { "self.%1N = %1N" },
field.name,
)
}
Expand Down Expand Up @@ -1224,10 +1258,9 @@ class SwiftGenerator private constructor(
if (isIndirect(type, field)) {
property.addAttribute(AttributeSpec.builder(indirect).build())
}
val default = field.default
if (default != null) {
val defaultValue = defaultFieldInitializer(field.type!!, default)
property.addAttribute(AttributeSpec.builder(defaulted).addArgument("defaultValue: $defaultValue").build())
val defaultedValue = field.defaultedValue
if (!isIndirect(type, field) && defaultedValue != null) {
property.addAttribute(AttributeSpec.builder(defaulted).addArgument("defaultValue: $defaultedValue").build())
}

if (field.isMap) {
Expand Down Expand Up @@ -1278,7 +1311,8 @@ class SwiftGenerator private constructor(
typeName == DOUBLE -> defaultValue.toDoubleFieldInitializer()
typeName == STRING -> CodeBlock.of("%S", stringLiteralWithQuotes2(defaultValue.toString()))
typeName == DATA -> CodeBlock.of(
"Foundation.Data(base64Encoded: %S)!",
"%T(base64Encoded: %S)!",
FOUNDATION_DATA,
defaultValue.toString().encode(charset = Charsets.ISO_8859_1).base64(),
)
protoType.isEnum -> CodeBlock.of("%T.%L", typeName, defaultValue)
Expand Down
7 changes: 4 additions & 3 deletions wire-tests-proto3-swift/src/main/swift/ContainsDuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Wire

public struct ContainsDuration {

@Defaulted(defaultValue: Duration())
public var duration: Duration?
public var unknownFields: Foundation.Data = .init()

Expand All @@ -20,7 +21,7 @@ extension ContainsDuration {
@_disfavoredOverload
@available(*, deprecated)
public init(duration: Duration? = nil) {
self.duration = duration
self._duration.wrappedValue = duration
}

}
Expand Down Expand Up @@ -63,7 +64,7 @@ extension ContainsDuration : Proto3Codable {
}
self.unknownFields = try protoReader.endMessage(token: token)

self.duration = duration
self._duration.wrappedValue = duration
}

public func encode(to protoWriter: Wire.ProtoWriter) throws {
Expand All @@ -78,7 +79,7 @@ extension ContainsDuration : Codable {

public init(from decoder: Swift.Decoder) throws {
let container = try decoder.container(keyedBy: Wire.StringLiteralCodingKeys.self)
self.duration = try container.decodeIfPresent(Duration.self, forKey: "duration")
self._duration.wrappedValue = try container.decodeIfPresent(Duration.self, forKey: "duration")
}

public func encode(to encoder: Swift.Encoder) throws {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Wire

public struct ContainsTimestamp {

@Defaulted(defaultValue: Timestamp())
public var timestamp: Timestamp?
public var unknownFields: Foundation.Data = .init()

Expand All @@ -20,7 +21,7 @@ extension ContainsTimestamp {
@_disfavoredOverload
@available(*, deprecated)
public init(timestamp: Timestamp? = nil) {
self.timestamp = timestamp
self._timestamp.wrappedValue = timestamp
}

}
Expand Down Expand Up @@ -63,7 +64,7 @@ extension ContainsTimestamp : Proto3Codable {
}
self.unknownFields = try protoReader.endMessage(token: token)

self.timestamp = timestamp
self._timestamp.wrappedValue = timestamp
}

public func encode(to protoWriter: Wire.ProtoWriter) throws {
Expand All @@ -78,7 +79,7 @@ extension ContainsTimestamp : Codable {

public init(from decoder: Swift.Decoder) throws {
let container = try decoder.container(keyedBy: Wire.StringLiteralCodingKeys.self)
self.timestamp = try container.decodeIfPresent(Timestamp.self, forKey: "timestamp")
self._timestamp.wrappedValue = try container.decodeIfPresent(Timestamp.self, forKey: "timestamp")
}

public func encode(to encoder: Swift.Encoder) throws {
Expand Down
Loading

0 comments on commit f2772d1

Please sign in to comment.