Skip to content

Commit

Permalink
Rename DecoratedField to ProtoBoeuf::CodeGen::Field
Browse files Browse the repository at this point in the history
  • Loading branch information
davebenvenuti committed Jan 31, 2025
1 parent 99ad480 commit 3b111ab
Show file tree
Hide file tree
Showing 7 changed files with 220 additions and 203 deletions.
2 changes: 1 addition & 1 deletion Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ end

Rake::TestTask.new do |t|
t.libs << "test"
t.test_files = FileList["test/*_test.rb"]
t.test_files = FileList["test/**/*_test.rb"]
t.verbose = true
end

Expand Down
11 changes: 6 additions & 5 deletions lib/protoboeuf/codegen.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

require "erb"
require "syntax_tree"
require_relative "codegen_type_helper"
require_relative "decorated_field"

module ProtoBoeuf
class CodeGen
require "protoboeuf/codegen/type_helper"
require "protoboeuf/codegen/field"

class EnumCompiler
attr_reader :generate_types

Expand Down Expand Up @@ -95,7 +96,7 @@ def initialize(message, toplevel_enums, generate_types:, requires:, syntax:, opt
@message = message
@optional_field_bit_lut = []
@fields = @message.field.map do |field|
DecoratedField.new(field:, message:, syntax:)
CodeGen::Field.new(field:, message:, syntax:)
end
@enum_field_types = toplevel_enums.merge(message.enum_type.group_by(&:name))
@requires = requires
Expand Down Expand Up @@ -136,7 +137,7 @@ def initialize(message, toplevel_enums, generate_types:, requires:, syntax:, opt
# list only contains non-proto3_optional fields, so we're using that
# to only iterate over actual "oneof" fields.
@oneof_selection_fields = @oneof_fields.each_with_index.map do |item, i|
item && DecoratedField.new(message:, field: message.oneof_decl[i], syntax:)
item && CodeGen::Field.new(message:, field: message.oneof_decl[i], syntax:)
end
end

Expand Down Expand Up @@ -799,7 +800,7 @@ def initialize_oneofs
end

def initialize_oneof(field, msg)
oneof = DecoratedField.new(message: msg, field: msg.oneof_decl[field.oneof_index], syntax:)
oneof = CodeGen::Field.new(message: msg, field: msg.oneof_decl[field.oneof_index], syntax:)

<<~RUBY
if #{lvar_read(field)} == nil
Expand Down
70 changes: 70 additions & 0 deletions lib/protoboeuf/codegen/field.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# frozen_string_literal: true

require "forwardable"

# Adds some convenience methods to Google::Protobuf::FieldDescriptorProto
module ProtoBoeuf
class CodeGen
class Field
attr_reader :original_field, :message, :syntax

extend Forwardable

def_delegators :@original_field,
:name,
:label,
:type_name,
:type,
:number,
:options,
:oneof_index,
:has_oneof_index?

def initialize(field:, message:, syntax:)
@original_field = field
@message = message
@syntax = syntax
end

def optional?
original_field.proto3_optional || (label == :LABEL_OPTIONAL && !proto3?)
end

def repeated?
label == :LABEL_REPEATED
end

def map_field?
return false unless repeated?

map_name = type_name.split(".").last
message.nested_type.any? { |type| type.name == map_name && type.options&.map_entry }
end

class MapType < Struct.new(:key, :value); end

def map_type
return false unless repeated?

map_name = type_name.split(".").last
message.nested_type.find { |type| type.name == map_name && type.options&.map_entry }.tap do |descriptor|
raise ArgumentError, "Not a map field" if descriptor.nil?

return MapType.new(
key: self.class.new(field: descriptor.field[0], message:, syntax:),
value: self.class.new(field: descriptor.field[1], message:, syntax:),
)
end
end

def proto3?
"proto3" == syntax
end

# Return an instance variable name for use in generated code
def iv_name
"@#{name}"
end
end
end
end
File renamed without changes.
58 changes: 0 additions & 58 deletions lib/protoboeuf/decorated_field.rb

This file was deleted.

143 changes: 143 additions & 0 deletions test/codegen/field_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# frozen_string_literal: true

require "helper"
require "protoboeuf/codegen/field"

module ProtoBoeuf
class CodeGen
class FieldTest < ProtoBoeuf::Test
def test_delegates_common_methods
fields = fields_for_proto_message(<<~PROTO)
syntax = "proto3";
message MyMessage {
optional int32 field1 = 1 [deprecated=true];
}
PROTO

field = fields["field1"]

[:name, :label, :type_name, :type, :number, :options, :oneof_index, :has_oneof_index?].each do |method_name|
assert_equal(
field.send(method_name),
field.original_field.send(method_name),
"expected CodeGen::Field to delegate ##{method_name}",
)
end
end

def test_proto2_optional
fields = fields_for_proto_message(<<~PROTO)
message MyMessage {
optional int32 field1 = 1;
required int32 field2 = 2;
}
PROTO

assert(fields["field1"].optional?, "field1 should be optional")
refute(fields["field2"].optional?, "field2 should not be optional")
end

def test_proto3_optional
fields = fields_for_proto_message(<<~PROTO)
syntax = "proto3";
message MyMessage {
optional int32 field1 = 1;
int32 field2 = 2;
}
PROTO

assert(fields["field1"].optional?, "field1 should be optional")
refute(fields["field2"].optional?, "field2 should not be optional")
end

def test_repeated
fields = fields_for_proto_message(<<~PROTO)
syntax = "proto3";
message MyMessage {
repeated int32 field1 = 1;
int32 field2 = 2;
}
PROTO

assert(fields["field1"].repeated?, "field1 should be repeated")
refute(fields["field2"].repeated?, "field2 should not be repeated")
end

def test_map_field?
fields = fields_for_proto_message(<<~PROTO)
syntax = "proto3";
message MyMessage {
map<string, string> field1 = 1;
int32 field2 = 2;
}
PROTO

assert(fields["field1"].map_field?, "field1 should be a map field")
refute(fields["field2"].map_field?, "field2 should not be a map field")
end

def test_map_type
fields = fields_for_proto_message(<<~PROTO)
syntax = "proto3";
message MyMessage {
map<string, int32> field1 = 1;
int32 field2 = 2;
}
PROTO

map_type = fields["field1"].map_type
assert_kind_of(CodeGen::Field::MapType, map_type)

assert_kind_of(CodeGen::Field, map_type.key)
assert_equal(:TYPE_STRING, map_type.key.type)

assert_kind_of(CodeGen::Field, map_type.value)
assert_equal(:TYPE_INT32, map_type.value.type)

refute(fields["field2"].map_field?, "field2 should not have a map_type")
end

def test_proto3?
proto_2_fields = fields_for_proto_message(<<~PROTO)
message MyMessage {
map<string, int32> field1 = 1;
}
PROTO

refute(proto_2_fields["field1"].proto3?, "CodeGen::Field#proto3? should return false for proto2")

proto_3_fields = fields_for_proto_message(<<~PROTO)
syntax = "proto3";
message MyMessage {
map<string, int32> field1 = 1;
}
PROTO

assert(proto_3_fields["field1"].proto3?, "CodeGen::Field#proto3? should return true for proto3")
end

private

def fields_for_proto_message(proto_string)
unit = parse_proto_string(proto_string)

syntax = unit.file.first.syntax
message = unit.file.first.message_type.first

message.field.group_by(&:name).transform_values do |fields|
CodeGen::Field.new(
field: fields.first,
message:,
syntax:,
)
end
end
end
end
end
Loading

0 comments on commit 3b111ab

Please sign in to comment.