Class: Google::Protobuf::Internal::FileBuilder

Inherits:
Object
  • Object
show all
Defined in:
lib/google/protobuf/descriptor_dsl.rb

Instance Method Summary collapse

Constructor Details

#initialize(pool, name, options = {}) ⇒ FileBuilder

Returns a new instance of FileBuilder.



76
77
78
79
80
81
82
# 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

Instance Method Details

#add_enum(name, &block) ⇒ Object



90
91
92
# 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) ⇒ Object



84
85
86
87
88
# 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

#buildObject



282
283
284
285
286
# File 'lib/google/protobuf/descriptor_dsl.rb', line 282

def build
  rewrite_enum_defaults
  fix_nesting
  return @file_proto
end

#fix_nestingObject



240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
# 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) ⇒ Object



232
233
234
235
236
237
238
# 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) ⇒ Object

The DSL can omit a package name; here we infer what the package is if was not specified.



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# 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_protoObject



278
279
280
# File 'lib/google/protobuf/descriptor_dsl.rb', line 278

def internal_file_proto
  @file_proto
end

#rewrite_enum_default(field) ⇒ Object



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# 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_defaultsObject

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.



175
176
177
178
179
180
181
# 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) ⇒ Object

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. */



222
223
224
225
226
227
228
229
230
# 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