class Google::Protobuf::Internal::FileBuilder

Public Class Methods

new(pool, name, options={}) click to toggle source
# 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

add_enum(name, &block) click to toggle source
# File lib/google/protobuf/descriptor_dsl.rb, line 90
def add_enum(name, &block)
  EnumBuilder.new(name, @file_proto).instance_eval(&block)
end
add_message(name, &block) click to toggle source
# 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
build() click to toggle source
# File lib/google/protobuf/descriptor_dsl.rb, line 282
def build
  rewrite_enum_defaults
  fix_nesting
  return @file_proto
end
fix_nesting() click to toggle source
# 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
get_parent_msg(msgs_by_name, name, parent_name) click to toggle source
# 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
infer_package(names) click to toggle source

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
internal_file_proto() click to toggle source
# File lib/google/protobuf/descriptor_dsl.rb, line 278
def internal_file_proto
  @file_proto
end
rewrite_enum_default(field) click to toggle source
# 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
rewrite_enum_defaults() click to toggle source

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
split_parent_name(msg_or_enum) click to toggle source

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