Code generate Swift wrappers for protocol buffers.

This commit is contained in:
Matthew Chen 2018-08-01 14:37:09 -04:00
parent 02a4de637e
commit 64c99988a7
4 changed files with 2103 additions and 192 deletions

View File

@ -196,15 +196,22 @@ class BaseContext(object):
return base_type
def can_field_be_optional(self, field):
if field.proto_type == 'uint64':
return False
elif field.proto_type == 'uint32':
return False
elif field.proto_type == 'fixed64':
return False
elif field.proto_type == 'bool':
return False
elif self.is_field_an_enum(field):
if field.proto_type in ('uint64',
'uint32',
'fixed64',
'bool', ):
return not field.is_required
# if field.proto_type == 'uint64':
# return False
# elif field.proto_type == 'uint32':
# return False
# elif field.proto_type == 'fixed64':
# return False
# elif field.proto_type == 'bool':
# return False
# elif self.is_field_an_enum(field):
if self.is_field_an_enum(field):
return False
else:
return True
@ -293,15 +300,23 @@ public enum %s: Error {
class MessageField:
def __init__(self, name, index, rules, proto_type, default_value, sort_index):
def __init__(self, name, index, rules, proto_type, default_value, sort_index, is_required):
self.name = name
self.index = index
self.rules = rules
self.proto_type = proto_type
self.default_value = default_value
self.sort_index = sort_index
self.is_required = is_required
def has_accessor_name(self):
name = 'has' + self.name_swift[0].upper() + self.name_swift[1:]
if name == 'hasId':
# TODO: I'm not sure why "Apple Swift Proto" code formats the
# the name in this way.
name = 'hasID'
return name
class MessageContext(BaseContext):
def __init__(self, args, parent, proto_name):
BaseContext.__init__(self)
@ -339,7 +354,7 @@ class MessageContext(BaseContext):
def generate(self, writer):
for child in self.messages:
child.generate(writer)
writer.add('// MARK: - %s' % self.swift_name)
writer.newline()
@ -351,10 +366,17 @@ class MessageContext(BaseContext):
for child in self.enums:
child.generate(writer)
wrapped_swift_name = self.derive_wrapped_swift_name()
# Prepare fields
for field in self.fields():
field.type_swift = self.swift_type_for_field(field)
field.name_swift = lowerCamlCaseForUnderscoredText_wrapped(field.name)
self.generate_builder(writer)
# writer.add('private let proto: %s' % wrapped_swift_name )
# writer.newline()
# Property Declarations
for field in self.fields():
@ -388,7 +410,6 @@ public func serializedData() throws -> Data {
writer.newline()
# parseData() func
wrapped_swift_name = self.derive_wrapped_swift_name()
writer.add('@objc public class func parseData(_ serializedData: Data) throws -> %s {' % self.swift_name)
writer.push_indent()
writer.add('let proto = try %s(serializedData: serializedData)' % ( wrapped_swift_name, ) )
@ -402,6 +423,25 @@ public func serializedData() throws -> Data {
writer.push_indent()
for field in self.fields():
if field.is_required:
# if self.can_field_be_optional(field):
writer.add('guard proto.%s else {' % field.has_accessor_name() )
writer.push_indent()
writer.add('throw %s.invalidProtobuf(description: "\(logTag) missing required field: %s")' % ( writer.invalid_protobuf_error_name, field.name_swift, ) )
writer.pop_indent()
writer.add('}')
if self.is_field_an_enum(field):
# TODO: Assert that rules is empty.
enum_context = self.context_for_proto_type(field)
writer.add('let %s = %sWrap(proto.%s)' % ( field.name_swift, enum_context.swift_name, field.name_swift, ) )
elif self.is_field_a_proto(field):
writer.add('let %s = try %s.parseProto(proto.%s)' % (field.name_swift, self.base_swift_type_for_field(field), field.name_swift)),
else:
writer.add('let %s = proto.%s' % ( field.name_swift, field.name_swift, ) )
writer.newline()
continue
default_value = self.default_value_for_field(field)
if default_value is None:
writer.add('var %s: %s' % (field.name_swift, field.type_swift))
@ -421,12 +461,7 @@ public func serializedData() throws -> Data {
writer.add('let wrapped = item')
writer.add('%s.append(wrapped)' % ( field.name_swift, ) )
else:
hasAccessor = 'has' + field.name_swift[0].upper() + field.name_swift[1:]
if hasAccessor == 'hasId':
# TODO: I'm not sure why "Apple Swift Proto" code formats the
# the name in this way.
hasAccessor = 'hasID'
writer.add('if proto.%s {' % hasAccessor)
writer.add('if proto.%s {' % field.has_accessor_name() )
writer.push_indent()
if self.is_field_an_enum(field):
@ -526,7 +561,140 @@ public func serializedData() throws -> Data {
writer.add('}')
writer.newline()
def generate_builder(self, writer):
wrapped_swift_name = self.derive_wrapped_swift_name()
writer.add('// MARK: - %sBuilder' % self.swift_name)
writer.newline()
writer.add('@objc public class %sBuilder: NSObject {' % self.swift_name)
writer.newline()
writer.push_context(self.proto_name, self.swift_name)
wrapped_swift_name = self.derive_wrapped_swift_name()
# Property Declarations
for field in self.fields():
if field.rules == 'repeated':
writer.add('private var %s: [%s]' % ( field.name_swift, self.base_swift_type_for_field(field), ))
else:
writer.add('private var %s: %s?' % ( field.name_swift, self.base_swift_type_for_field(field), ))
writer.newline()
# Initializer
writer.add('@objc public override init() {}')
writer.newline()
# Setters
for field in self.fields():
if field.rules == 'repeated':
accessor_name = field.name_swift
accessor_name = 'add' + accessor_name[0].upper() + accessor_name[1:]
writer.add('@objc public func %s(_ value: %s) {' % ( accessor_name, self.base_swift_type_for_field(field), ))
writer.push_indent()
writer.add('%s.append(value)' % ( field.name_swift, ) )
writer.pop_indent()
writer.add('}')
writer.newline()
else:
accessor_name = field.name_swift
accessor_name = 'set' + accessor_name[0].upper() + accessor_name[1:]
writer.add('@objc public func %s(_ value: %s) {' % ( accessor_name, self.base_swift_type_for_field(field), ))
writer.push_indent()
writer.add('%s = value' % ( field.name_swift, ) )
writer.pop_indent()
writer.add('}')
writer.newline()
# build() func
writer.add('@objc public func build() throws -> %s {' % self.swift_name)
writer.push_indent()
writer.add('let proto = %s.with { (builder) in' % wrapped_swift_name)
writer.push_indent()
for field in self.fields():
if field.rules == 'repeated':
accessor_name = field.name_swift
accessor_name = 'add' + accessor_name[0].upper() + accessor_name[1:]
writer.add('for item in %s {' % (field.name_swift))
writer.push_indent()
if self.is_field_an_enum(field):
enum_context = self.context_for_proto_type(field)
writer.add('builder.%s(%sUnwrap(item))' % ( accessor_name, enum_context.swift_name, ) )
elif self.is_field_a_proto(field):
writer.add('builder.%s(item.asProtobuf)' % accessor_name)
else:
writer.add('builder.%s(item)' % accessor_name)
writer.pop_indent()
writer.add('}')
else:
writer.add('if let %s = self.%s {' % ( field.name_swift, field.name_swift, ))
writer.push_indent()
if self.is_field_an_enum(field):
enum_context = self.context_for_proto_type(field)
writer.add('builder.%s = %sUnwrap(%s)' % ( field.name_swift, enum_context.swift_name, field.name_swift, ) )
elif self.is_field_a_proto(field):
writer.add('builder.%s = %s.asProtobuf' % ( field.name_swift, field.name_swift, ) )
else:
writer.add('builder.%s = %s' % ( field.name_swift, field.name_swift, ) )
writer.pop_indent()
writer.add('}')
# if self.is_field_an_enum(field):
# # TODO: Assert that rules is empty.
# enum_context = self.context_for_proto_type(field)
# writer.add('builder.%s = %s.%sUnwrap(self.%s)' % ( field.name_swift, self.swift_name, enum_context.swift_name, field.name_swift, ) )
# elif field.rules == 'repeated':
# # TODO: Assert that type is a message.
# list_wrapped_swift_name = None
# if self.is_field_a_proto(field):
# message_context = self.context_for_proto_type(field)
# list_wrapped_swift_name = message_context.derive_wrapped_swift_name()
# else:
# # TODO: Assert not an enum.
# list_wrapped_swift_name = self.base_swift_type_for_field(field)
# writer.add('var %sUnwrapped = [%s]()' % (field.name_swift, list_wrapped_swift_name))
# writer.add('for item in %s {' % (field.name_swift))
# writer.push_indent()
# if self.is_field_a_proto(field):
# writer.add('%sUnwrapped.append(item.asProtobuf)' % field.name_swift)
# else:
# writer.add('%sUnwrapped.append(item)' % field.name_swift)
# writer.pop_indent()
# writer.add('}')
# writer.add('builder.%s = %sUnwrapped' % (field.name_swift, field.name_swift))
# elif field.rules == 'optional' and self.can_field_be_optional(field):
# writer.add('if let %s = self.%s {' % (field.name_swift, field.name_swift))
# writer.push_indent()
# if self.is_field_a_proto(field):
# writer.add('builder.%s = %s.asProtobuf' % (field.name_swift, field.name_swift))
# else:
# writer.add('builder.%s = %s' % (field.name_swift, field.name_swift))
# writer.pop_indent()
# writer.add('}')
# else:
# writer.add('builder.%s = self.%s' % (field.name_swift, field.name_swift))
# writer.newline()
# writer.add('self.%s = %s' % (field.name_swift, field.name_swift))
writer.rstrip()
writer.pop_indent()
writer.add('}')
writer.newline()
# writer.add('let protoData = try proto.serializedData()')
writer.add('let wrapper = try %s.parseProto(proto)' % self.swift_name)
writer.add('return wrapper')
writer.pop_indent()
writer.add('}')
writer.newline()
writer.pop_context()
writer.rstrip()
writer.add('}')
writer.newline()
class EnumContext(BaseContext):
def __init__(self, args, parent, proto_name):
BaseContext.__init__(self)
@ -613,25 +781,42 @@ class EnumContext(BaseContext):
writer.add('}')
writer.newline()
def line_parser(text):
# lineParser = LineParser(text.split('\n'))
class LineParser:
def __init__(self, text):
self.lines = text.split('\n')
self.lines.reverse()
self.next_line_comments = []
for line in text.split('\n'):
line = line.strip()
# if not line:
# continue
def next(self):
# lineParser = LineParser(text.split('\n'))
self.next_line_comments = []
while len(self.lines) > 0:
line = self.lines.pop()
line = line.strip()
# if not line:
# continue
comment_index = line.find('//')
if comment_index >= 0:
line = line[:comment_index].strip()
if not line:
continue
comment_index = line.find('//')
if comment_index >= 0:
comment = line[comment_index + len('//'):].strip()
line = line[:comment_index].strip()
if not line:
if comment:
self.next_line_comments.append(comment)
else:
if not line:
self.next_line_comments = []
if not line:
continue
if args.verbose:
print 'line:', line
# if args.verbose:
# print 'line:', line
yield line
return line
raise StopIteration()
def parse_enum(args, proto_file_path, parser, parent_context, enum_name):
@ -648,8 +833,8 @@ def parse_enum(args, proto_file_path, parser, parent_context, enum_name):
raise Exception('Incomplete enum: %s' % proto_file_path)
if line == '}':
if args.verbose:
print
# if args.verbose:
# print
parent_context.enums.append(context)
return
@ -696,9 +881,11 @@ def parse_message(args, proto_file_path, parser, parent_context, message_name):
except StopIteration:
raise Exception('Incomplete message: %s' % proto_file_path)
field_comments = parser.next_line_comments
if line == '}':
if args.verbose:
print
# if args.verbose:
# print
parent_context.messages.append(context)
return
@ -743,6 +930,7 @@ def parse_message(args, proto_file_path, parser, parent_context, message_name):
'name': item_name,
'index': item_index,
'default': item_default,
'field_comments': field_comments,
}
# print 'message_field:', message_field
@ -757,7 +945,10 @@ def parse_message(args, proto_file_path, parser, parent_context, message_name):
raise Exception('Duplicate message field index[%s]: %s' % (proto_file_path, item_name))
# context.field_indices.add(item_index)
context.field_map[item_index] = MessageField(item_name, item_index, item_rules, item_type, item_default, sort_index)
is_required = '@required' in field_comments
# if is_required:
# print 'is_required:', item_name
context.field_map[item_index] = MessageField(item_name, item_index, item_rules, item_type, item_default, sort_index, is_required)
sort_index = sort_index + 1
@ -815,7 +1006,7 @@ def process_proto_file(args, proto_file_path, dst_file_path):
package_regex = re.compile(r'^package\s+(.+);')
option_regex = re.compile(r'^option ')
parser = line_parser(text)
parser = LineParser(text)
# lineParser = LineParser(text.split('\n'))

View File

@ -23,10 +23,14 @@ message Envelope {
RECEIPT = 5;
}
// @required
optional Type type = 1;
// @required
optional string source = 2;
// @required
optional uint32 sourceDevice = 7;
optional string relay = 3;
// @required
optional uint64 timestamp = 5;
optional bytes legacyMessage = 6; // Contains an encrypted DataMessage
optional bytes content = 8; // Contains an encrypted Content
@ -42,6 +46,7 @@ message Content {
message CallMessage {
message Offer {
// @required
optional uint64 id = 1;
// Signal-iOS renamed the description field to avoid
// conflicts with [NSObject description].
@ -49,6 +54,7 @@ message CallMessage {
}
message Answer {
// @required
optional uint64 id = 1;
// Signal-iOS renamed the description field to avoid
// conflicts with [NSObject description].
@ -56,6 +62,7 @@ message CallMessage {
}
message IceUpdate {
// @required
optional uint64 id = 1;
optional string sdpMid = 2;
optional uint32 sdpMLineIndex = 3;
@ -63,10 +70,12 @@ message CallMessage {
}
message Busy {
// @required
optional uint64 id = 1;
}
message Hangup {
// @required
optional uint64 id = 1;
}
@ -100,7 +109,9 @@ message DataMessage {
optional uint32 flags = 4;
}
// @required
optional uint64 id = 1;
// @required
optional string author = 2;
optional string text = 3;
repeated QuotedAttachment attachments = 4;
@ -124,6 +135,7 @@ message DataMessage {
CUSTOM = 4;
}
// @required
optional string value = 1;
optional Type type = 2;
optional string label = 3;
@ -137,6 +149,7 @@ message DataMessage {
CUSTOM = 4;
}
// @required
optional string value = 1;
optional Type type = 2;
optional string label = 3;
@ -194,6 +207,7 @@ message ReceiptMessage {
READ = 1;
}
// @required
optional Type type = 1;
repeated uint64 timestamp = 2;
}
@ -205,6 +219,7 @@ message Verified {
UNVERIFIED = 2;
}
// @required
optional string destination = 1;
optional bytes identityKey = 2;
optional State state = 3;
@ -220,6 +235,7 @@ message SyncMessage {
}
message Contacts {
// @required
optional AttachmentPointer blob = 1;
// Signal-iOS renamed this property.
optional bool isComplete = 2 [default = false];
@ -242,11 +258,14 @@ message SyncMessage {
CONFIGURATION = 4;
}
// @required
optional Type type = 1;
}
message Read {
// @required
optional string sender = 1;
// @required
optional uint64 timestamp = 2;
}
@ -270,6 +289,7 @@ message AttachmentPointer {
VOICE_MESSAGE = 1;
}
// @required
optional fixed64 id = 1;
optional string contentType = 2;
optional bytes key = 3;
@ -290,7 +310,9 @@ message GroupContext {
QUIT = 3;
REQUEST_INFO = 4;
}
// @required
optional bytes id = 1;
// @required
optional Type type = 2;
optional string name = 3;
repeated string members = 4;
@ -303,6 +325,7 @@ message ContactDetails {
optional uint32 length = 2;
}
// @required
optional string number = 1;
optional string name = 2;
optional Avatar avatar = 3;
@ -319,6 +342,7 @@ message GroupDetails {
optional uint32 length = 2;
}
// @required
optional bytes id = 1;
optional string name = 2;
repeated string members = 3;

File diff suppressed because it is too large Load Diff

View File

@ -32,6 +32,7 @@ struct SignalServiceProtos_Envelope {
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
/// @required
var type: SignalServiceProtos_Envelope.TypeEnum {
get {return _type ?? .unknown}
set {_type = newValue}
@ -41,6 +42,7 @@ struct SignalServiceProtos_Envelope {
/// Clears the value of `type`. Subsequent reads from it will return its default value.
mutating func clearType() {self._type = nil}
/// @required
var source: String {
get {return _source ?? String()}
set {_source = newValue}
@ -50,6 +52,7 @@ struct SignalServiceProtos_Envelope {
/// Clears the value of `source`. Subsequent reads from it will return its default value.
mutating func clearSource() {self._source = nil}
/// @required
var sourceDevice: UInt32 {
get {return _sourceDevice ?? 0}
set {_sourceDevice = newValue}
@ -68,6 +71,7 @@ struct SignalServiceProtos_Envelope {
/// Clears the value of `relay`. Subsequent reads from it will return its default value.
mutating func clearRelay() {self._relay = nil}
/// @required
var timestamp: UInt64 {
get {return _timestamp ?? 0}
set {_timestamp = newValue}
@ -266,6 +270,7 @@ struct SignalServiceProtos_CallMessage {
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
/// @required
var id: UInt64 {
get {return _id ?? 0}
set {_id = newValue}
@ -299,6 +304,7 @@ struct SignalServiceProtos_CallMessage {
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
/// @required
var id: UInt64 {
get {return _id ?? 0}
set {_id = newValue}
@ -332,6 +338,7 @@ struct SignalServiceProtos_CallMessage {
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
/// @required
var id: UInt64 {
get {return _id ?? 0}
set {_id = newValue}
@ -383,6 +390,7 @@ struct SignalServiceProtos_CallMessage {
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
/// @required
var id: UInt64 {
get {return _id ?? 0}
set {_id = newValue}
@ -404,6 +412,7 @@ struct SignalServiceProtos_CallMessage {
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
/// @required
var id: UInt64 {
get {return _id ?? 0}
set {_id = newValue}
@ -539,6 +548,7 @@ struct SignalServiceProtos_DataMessage {
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
/// @required
var id: UInt64 {
get {return _id ?? 0}
set {_id = newValue}
@ -548,6 +558,7 @@ struct SignalServiceProtos_DataMessage {
/// Clears the value of `id`. Subsequent reads from it will return its default value.
mutating func clearID() {self._id = nil}
/// @required
var author: String {
get {return _author ?? String()}
set {_author = newValue}
@ -773,6 +784,7 @@ struct SignalServiceProtos_DataMessage {
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
/// @required
var value: String {
get {return _value ?? String()}
set {_value = newValue}
@ -846,6 +858,7 @@ struct SignalServiceProtos_DataMessage {
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
/// @required
var value: String {
get {return _value ?? String()}
set {_value = newValue}
@ -1110,6 +1123,7 @@ struct SignalServiceProtos_ReceiptMessage {
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
/// @required
var type: SignalServiceProtos_ReceiptMessage.TypeEnum {
get {return _type ?? .delivery}
set {_type = newValue}
@ -1159,6 +1173,7 @@ struct SignalServiceProtos_Verified {
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
/// @required
var destination: String {
get {return _destination ?? String()}
set {_destination = newValue}
@ -1371,6 +1386,7 @@ struct SignalServiceProtos_SyncMessage {
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
/// @required
var blob: SignalServiceProtos_AttachmentPointer {
get {return _storage._blob ?? SignalServiceProtos_AttachmentPointer()}
set {_uniqueStorage()._blob = newValue}
@ -1435,6 +1451,7 @@ struct SignalServiceProtos_SyncMessage {
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
/// @required
var type: SignalServiceProtos_SyncMessage.Request.TypeEnum {
get {return _type ?? .unknown}
set {_type = newValue}
@ -1491,6 +1508,7 @@ struct SignalServiceProtos_SyncMessage {
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
/// @required
var sender: String {
get {return _sender ?? String()}
set {_sender = newValue}
@ -1500,6 +1518,7 @@ struct SignalServiceProtos_SyncMessage {
/// Clears the value of `sender`. Subsequent reads from it will return its default value.
mutating func clearSender() {self._sender = nil}
/// @required
var timestamp: UInt64 {
get {return _timestamp ?? 0}
set {_timestamp = newValue}
@ -1548,6 +1567,7 @@ struct SignalServiceProtos_AttachmentPointer {
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
/// @required
var id: UInt64 {
get {return _id ?? 0}
set {_id = newValue}
@ -1682,6 +1702,7 @@ struct SignalServiceProtos_GroupContext {
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
/// @required
var id: Data {
get {return _storage._id ?? SwiftProtobuf.Internal.emptyData}
set {_uniqueStorage()._id = newValue}
@ -1691,6 +1712,7 @@ struct SignalServiceProtos_GroupContext {
/// Clears the value of `id`. Subsequent reads from it will return its default value.
mutating func clearID() {_storage._id = nil}
/// @required
var type: SignalServiceProtos_GroupContext.TypeEnum {
get {return _storage._type ?? .unknown}
set {_uniqueStorage()._type = newValue}
@ -1770,6 +1792,7 @@ struct SignalServiceProtos_ContactDetails {
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
/// @required
var number: String {
get {return _storage._number ?? String()}
set {_uniqueStorage()._number = newValue}
@ -1885,6 +1908,7 @@ struct SignalServiceProtos_GroupDetails {
// `Message` and `Message+*Additions` files in the SwiftProtobuf library for
// methods supported on all messages.
/// @required
var id: Data {
get {return _storage._id ?? SwiftProtobuf.Internal.emptyData}
set {_uniqueStorage()._id = newValue}