class Google::Protobuf::Internal::FileBuilder
Public Class Methods
# File lib/google/protobuf/descriptor_dsl.rb, line 76 def initialize(pool, name, options={}) @pool = pool @file_proto = Google::Protobuf::FileDescriptorProto.new( name: name, syntax: options.fetch(:syntax, "proto3") ) end
Public Instance Methods
# File lib/google/protobuf/descriptor_dsl.rb, line 90 def add_enum(name, &block) EnumBuilder.new(name, @file_proto).instance_eval(&block) end
# File lib/google/protobuf/descriptor_dsl.rb, line 84 def add_message(name, &block) builder = MessageBuilder.new(name, self, @file_proto) builder.instance_eval(&block) builder.internal_add_synthetic_oneofs end
# File lib/google/protobuf/descriptor_dsl.rb, line 282 def build rewrite_enum_defaults fix_nesting return @file_proto end
# File lib/google/protobuf/descriptor_dsl.rb, line 240 def fix_nesting # Calculate and update package. msgs_by_name = @file_proto.message_type.map { |msg| [msg.name, msg] }.to_h enum_names = @file_proto.enum_type.map { |enum_proto| enum_proto.name } package = infer_package(msgs_by_name.keys + enum_names) if package @file_proto.package = package end # Update nesting based on package. final_msgs = Google::Protobuf::RepeatedField.new(:message, Google::Protobuf::DescriptorProto) final_enums = Google::Protobuf::RepeatedField.new(:message, Google::Protobuf::EnumDescriptorProto) # Note: We don't iterate over msgs_by_name.values because we want to # preserve order as listed in the DSL. @file_proto.message_type.each { |msg| parent_name, msg.name = split_parent_name(msg) if parent_name == package final_msgs << msg else get_parent_msg(msgs_by_name, msg.name, parent_name).nested_type << msg end } @file_proto.enum_type.each { |enum| parent_name, enum.name = split_parent_name(enum) if parent_name == package final_enums << enum else get_parent_msg(msgs_by_name, enum.name, parent_name).enum_type << enum end } @file_proto.message_type = final_msgs @file_proto.enum_type = final_enums end
# File lib/google/protobuf/descriptor_dsl.rb, line 232 def get_parent_msg(msgs_by_name, name, parent_name) parent_msg = msgs_by_name[parent_name] if parent_msg.nil? raise "To define name #{name}, there must be a message named #{parent_name} to enclose it" end return parent_msg end
The DSL can omit a package name; here we infer what the package is if was not specified.
# File lib/google/protobuf/descriptor_dsl.rb, line 101 def infer_package(names) # Package is longest common prefix ending in '.', if any. if not names.empty? min, max = names.minmax last_common_dot = nil min.size.times { |i| if min[i] != max[i] then break end if min[i] == "." then last_common_dot = i end } if last_common_dot return min.slice(0, last_common_dot) end end nil end
# File lib/google/protobuf/descriptor_dsl.rb, line 278 def internal_file_proto @file_proto end
# File lib/google/protobuf/descriptor_dsl.rb, line 118 def rewrite_enum_default(field) if field.type != :TYPE_ENUM or !field.has_default_value? or !field.has_type_name? return end value = field.default_value type_name = field.type_name if value.empty? or value[0].ord < "0".ord or value[0].ord > "9".ord return end if type_name.empty? || type_name[0] != "." return end type_name = type_name[1..-1] as_int = Integer(value) rescue return enum_desc = @pool.lookup(type_name) if enum_desc.is_a?(Google::Protobuf::EnumDescriptor) # Enum was defined in a previous file. name = enum_desc.lookup_value(as_int) if name # Update the default value in the proto. field.default_value = name end else # See if enum was defined in this file. @file_proto.enum_type.each { |enum_proto| if enum_proto.name == type_name enum_proto.value.each { |enum_value_proto| if enum_value_proto.number == as_int # Update the default value in the proto. field.default_value = enum_value_proto.name return end } # We found the right enum, but no value matched. return end } end end
Historically we allowed enum defaults to be specified as a number. In retrospect this was a mistake as descriptors require defaults to be specified as a label. This can make a difference if multiple labels have the same number.
Here we do a pass over all enum defaults and rewrite numeric defaults by looking up their labels. This is complicated by the fact that the enum definition can live in either the symtab or the file_proto.
We take advantage of the fact that this is called before enums or messages are nested in other messages, so we only have to iterate one level deep.
# File lib/google/protobuf/descriptor_dsl.rb, line 175 def rewrite_enum_defaults @file_proto.message_type.each { |msg| msg.field.each { |field| rewrite_enum_default(field) } } end
We have to do some relatively complicated logic here for backward compatibility.
In descriptor.proto, messages are nested inside other messages if that is what the original .proto file looks like. For example, suppose we have this foo.proto:
package foo; message Bar {
message Baz {}
}
The descriptor for this must look like this:
file {
name: "test.proto" package: "foo" message_type { name: "Bar" nested_type { name: "Baz" } }
}
However, the Ruby generated code has always generated messages in a flat, non-nested way:
Google::Protobuf::DescriptorPool.generated_pool.build do
add_message "foo.Bar" do end add_message "foo.Bar.Baz" do end
end
Here we need to do a translation where we turn this generated code into the above descriptor. We need to infer that “foo” is the package name, and not a message itself. */
# File lib/google/protobuf/descriptor_dsl.rb, line 222 def split_parent_name(msg_or_enum) name = msg_or_enum.name idx = name.rindex(?.) if idx return name[0...idx], name[idx+1..-1] else return nil, name end end