Add wrapper type helpers for Ruby (#5739)

* add wrapper type helpers

* add check for _as_value suffix
This commit is contained in:
Joe Bolinger 2019-03-27 09:44:43 -07:00 committed by Paul Yang
parent d2daa38986
commit e4bbca1fc5
6 changed files with 289 additions and 2 deletions

1
ruby/.gitignore vendored
View File

@ -6,3 +6,4 @@ protobuf-jruby.iml
target/
pkg/
tmp/
tests/google/

View File

@ -93,6 +93,7 @@ genproto_output << "tests/test_ruby_package.rb"
genproto_output << "tests/test_ruby_package_proto2.rb"
genproto_output << "tests/basic_test.rb"
genproto_output << "tests/basic_test_proto2.rb"
genproto_output << "tests/wrappers.rb"
file "tests/generated_code.rb" => "tests/generated_code.proto" do |file_task|
sh "../src/protoc --ruby_out=. tests/generated_code.proto"
end
@ -125,6 +126,10 @@ file "tests/basic_test_proto2.rb" => "tests/basic_test_proto2.proto" do |file_ta
sh "../src/protoc -I../src -I. --ruby_out=. tests/basic_test_proto2.proto"
end
file "tests/wrappers.rb" => "../src/google/protobuf/wrappers.proto" do |file_task|
sh "../src/protoc -I../src -I. --ruby_out=tests ../src/google/protobuf/wrappers.proto"
end
task :genproto => genproto_output
task :clean do

View File

@ -119,9 +119,37 @@ enum {
METHOD_SETTER = 2,
METHOD_CLEAR = 3,
METHOD_PRESENCE = 4,
METHOD_ENUM_GETTER = 5
METHOD_ENUM_GETTER = 5,
METHOD_WRAPPER_GETTER = 6,
METHOD_WRAPPER_SETTER = 7
};
// Check if the field is a well known wrapper type
static bool is_wrapper_type_field(const upb_fielddef* field) {
char* field_type_name = rb_class2name(field_type_class(field));
return strcmp(field_type_name, "Google::Protobuf::DoubleValue") == 0 ||
strcmp(field_type_name, "Google::Protobuf::FloatValue") == 0 ||
strcmp(field_type_name, "Google::Protobuf::Int32Value") == 0 ||
strcmp(field_type_name, "Google::Protobuf::Int64Value") == 0 ||
strcmp(field_type_name, "Google::Protobuf::UInt32Value") == 0 ||
strcmp(field_type_name, "Google::Protobuf::UInt64Value") == 0 ||
strcmp(field_type_name, "Google::Protobuf::BoolValue") == 0 ||
strcmp(field_type_name, "Google::Protobuf::StringValue") == 0 ||
strcmp(field_type_name, "Google::Protobuf::BytesValue") == 0;
}
// Get a new Ruby wrapper type and set the initial value
static VALUE ruby_wrapper_type(const upb_fielddef* field, const VALUE* value) {
if (is_wrapper_type_field(field) && value != Qnil) {
VALUE hash = rb_hash_new();
rb_hash_aset(hash, rb_str_new2("value"), value);
VALUE args[1] = { hash };
return rb_class_new_instance(1, args, field_type_class(field));
}
return Qnil;
}
static int extract_method_call(VALUE method_name, MessageHeader* self,
const upb_fielddef **f, const upb_oneofdef **o) {
Check_Type(method_name, T_SYMBOL);
@ -157,6 +185,34 @@ static int extract_method_call(VALUE method_name, MessageHeader* self,
bool has_field = upb_msgdef_lookupname(self->descriptor->msgdef, name, name_len,
&test_f, &test_o);
// Look for wrapper type accessor of the form <field_name>_as_value
if (!has_field &&
(accessor_type == METHOD_GETTER || accessor_type == METHOD_SETTER) &&
name_len > 9 && strncmp(name + name_len - 9, "_as_value", 9) == 0) {
// Find the field name
char wrapper_field_name[name_len - 8];
strncpy(wrapper_field_name, name, name_len - 9);
wrapper_field_name[name_len - 7] = '\0';
// Check if field exists and is a wrapper type
const upb_oneofdef* test_o_wrapper;
const upb_fielddef* test_f_wrapper;
if (upb_msgdef_lookupname(self->descriptor->msgdef, wrapper_field_name, name_len - 9,
&test_f_wrapper, &test_o_wrapper) &&
upb_fielddef_type(test_f_wrapper) == UPB_TYPE_MESSAGE &&
is_wrapper_type_field(test_f_wrapper)) {
// It does exist!
has_field = true;
if (accessor_type == METHOD_SETTER) {
accessor_type = METHOD_WRAPPER_SETTER;
} else {
accessor_type = METHOD_WRAPPER_GETTER;
}
test_o = test_o_wrapper;
test_f = test_f_wrapper;
}
}
// Look for enum accessor of the form <enum_name>_const
if (!has_field && accessor_type == METHOD_GETTER &&
name_len > 6 && strncmp(name + name_len - 6, "_const", 6) == 0) {
@ -238,7 +294,7 @@ VALUE Message_method_missing(int argc, VALUE* argv, VALUE _self) {
int accessor_type = extract_method_call(argv[0], self, &f, &o);
if (accessor_type == METHOD_UNKNOWN || (o == NULL && f == NULL) ) {
return rb_call_super(argc, argv);
} else if (accessor_type == METHOD_SETTER) {
} else if (accessor_type == METHOD_SETTER || accessor_type == METHOD_WRAPPER_SETTER) {
if (argc != 2) {
rb_raise(rb_eArgError, "Expected 2 arguments, received %d", argc);
}
@ -275,6 +331,16 @@ VALUE Message_method_missing(int argc, VALUE* argv, VALUE _self) {
return Qnil;
} else if (accessor_type == METHOD_PRESENCE) {
return layout_has(self->descriptor->layout, Message_data(self), f);
} else if (accessor_type == METHOD_WRAPPER_GETTER) {
VALUE value = layout_get(self->descriptor->layout, Message_data(self), f);
if (value != Qnil) {
value = rb_funcall(value, rb_intern("value"), 0);
}
return value;
} else if (accessor_type == METHOD_WRAPPER_SETTER) {
VALUE wrapper = ruby_wrapper_type(f, argv[1]);
layout_set(self->descriptor->layout, Message_data(self), f, wrapper);
return Qnil;
} else if (accessor_type == METHOD_ENUM_GETTER) {
VALUE enum_type = field_type_class(f);
VALUE method = rb_intern("const_get");

View File

@ -2,6 +2,7 @@ syntax = "proto3";
package basic_test;
import "google/protobuf/wrappers.proto";
import "google/protobuf/timestamp.proto";
import "google/protobuf/duration.proto";
import "google/protobuf/struct.proto";
@ -112,6 +113,22 @@ message Outer {
message Inner {
}
message Wrapper {
google.protobuf.DoubleValue double = 1;
google.protobuf.FloatValue float = 2;
google.protobuf.Int32Value int32 = 3;
google.protobuf.Int64Value int64 = 4;
google.protobuf.UInt32Value uint32 = 5;
google.protobuf.UInt64Value uint64 = 6;
google.protobuf.BoolValue bool = 7;
google.protobuf.StringValue string = 8;
google.protobuf.BytesValue bytes = 9;
string real_string = 100;
oneof a_oneof {
string oneof_string = 10;
}
}
message TimeMessage {
google.protobuf.Timestamp timestamp = 1;
google.protobuf.Duration duration = 2;

View File

@ -2,6 +2,7 @@ syntax = "proto2";
package basic_test_proto2;
import "google/protobuf/wrappers.proto";
import "google/protobuf/timestamp.proto";
import "google/protobuf/duration.proto";
import "google/protobuf/struct.proto";
@ -120,6 +121,22 @@ message OneofMessage {
}
}
message Wrapper {
optional google.protobuf.DoubleValue double = 1;
optional google.protobuf.FloatValue float = 2;
optional google.protobuf.Int32Value int32 = 3;
optional google.protobuf.Int64Value int64 = 4;
optional google.protobuf.UInt32Value uint32 = 5;
optional google.protobuf.UInt64Value uint64 = 6;
optional google.protobuf.BoolValue bool = 7;
optional google.protobuf.StringValue string = 8;
optional google.protobuf.BytesValue bytes = 9;
optional string real_string = 100;
oneof a_oneof {
string oneof_string = 10;
}
}
message TimeMessage {
optional google.protobuf.Timestamp timestamp = 1;
optional google.protobuf.Duration duration = 2;

View File

@ -1,3 +1,5 @@
require 'google/protobuf/wrappers_pb.rb'
# Defines tests which are common between proto2 and proto3 syntax.
#
# Requires that the proto messages are exactly the same in proto2 and proto3 syntax
@ -1267,6 +1269,185 @@ module CommonTests
assert proto_module::TestMessage.new != nil
end
def test_wrapper_getters
m = proto_module::Wrapper.new(
double: Google::Protobuf::DoubleValue.new(value: 2.0),
float: Google::Protobuf::FloatValue.new(value: 4.0),
int32: Google::Protobuf::Int32Value.new(value: 3),
int64: Google::Protobuf::Int64Value.new(value: 4),
uint32: Google::Protobuf::UInt32Value.new(value: 5),
uint64: Google::Protobuf::UInt64Value.new(value: 6),
bool: Google::Protobuf::BoolValue.new(value: true),
string: Google::Protobuf::StringValue.new(value: 'str'),
bytes: Google::Protobuf::BytesValue.new(value: 'fun'),
real_string: '100'
)
assert_equal 2.0, m.double_as_value
assert_equal 2.0, m.double.value
assert_equal 4.0, m.float_as_value
assert_equal 4.0, m.float.value
assert_equal 3, m.int32_as_value
assert_equal 3, m.int32.value
assert_equal 4, m.int64_as_value
assert_equal 4, m.int64.value
assert_equal 5, m.uint32_as_value
assert_equal 5, m.uint32.value
assert_equal 6, m.uint64_as_value
assert_equal 6, m.uint64.value
assert_equal true, m.bool_as_value
assert_equal true, m.bool.value
assert_equal 'str', m.string_as_value
assert_equal 'str', m.string.value
assert_equal 'fun', m.bytes_as_value
assert_equal 'fun', m.bytes.value
end
def test_wrapper_setters_as_value
m = proto_module::Wrapper.new
m.double_as_value = 4.8
assert_equal 4.8, m.double_as_value
assert_equal Google::Protobuf::DoubleValue.new(value: 4.8), m.double
m.float_as_value = 2.4
assert_in_delta 2.4, m.float_as_value
assert_in_delta Google::Protobuf::FloatValue.new(value: 2.4).value, m.float.value
m.int32_as_value = 5
assert_equal 5, m.int32_as_value
assert_equal Google::Protobuf::Int32Value.new(value: 5), m.int32
m.int64_as_value = 15
assert_equal 15, m.int64_as_value
assert_equal Google::Protobuf::Int64Value.new(value: 15), m.int64
m.uint32_as_value = 50
assert_equal 50, m.uint32_as_value
assert_equal Google::Protobuf::UInt32Value.new(value: 50), m.uint32
m.uint64_as_value = 500
assert_equal 500, m.uint64_as_value
assert_equal Google::Protobuf::UInt64Value.new(value: 500), m.uint64
m.bool_as_value = false
assert_equal false, m.bool_as_value
assert_equal Google::Protobuf::BoolValue.new(value: false), m.bool
m.string_as_value = 'xy'
assert_equal 'xy', m.string_as_value
assert_equal Google::Protobuf::StringValue.new(value: 'xy'), m.string
m.bytes_as_value = '123'
assert_equal '123', m.bytes_as_value
assert_equal Google::Protobuf::BytesValue.new(value: '123'), m.bytes
m.double_as_value = nil
assert_nil m.double
assert_nil m.double_as_value
m.float_as_value = nil
assert_nil m.float
assert_nil m.float_as_value
m.int32_as_value = nil
assert_nil m.int32
assert_nil m.int32_as_value
m.int64_as_value = nil
assert_nil m.int64
assert_nil m.int64_as_value
m.uint32_as_value = nil
assert_nil m.uint32
assert_nil m.uint32_as_value
m.uint64_as_value = nil
assert_nil m.uint64
assert_nil m.uint64_as_value
m.bool_as_value = nil
assert_nil m.bool
assert_nil m.bool_as_value
m.string_as_value = nil
assert_nil m.string
assert_nil m.string_as_value
m.bytes_as_value = nil
assert_nil m.bytes
assert_nil m.bytes_as_value
end
def test_wrapper_setters
m = proto_module::Wrapper.new
m.double = Google::Protobuf::DoubleValue.new(value: 4.8)
assert_equal 4.8, m.double_as_value
assert_equal Google::Protobuf::DoubleValue.new(value: 4.8), m.double
m.float = Google::Protobuf::FloatValue.new(value: 2.4)
assert_in_delta 2.4, m.float_as_value
assert_in_delta Google::Protobuf::FloatValue.new(value: 2.4).value, m.float.value
m.int32 = Google::Protobuf::Int32Value.new(value: 5)
assert_equal 5, m.int32_as_value
assert_equal Google::Protobuf::Int32Value.new(value: 5), m.int32
m.int64 = Google::Protobuf::Int64Value.new(value: 15)
assert_equal 15, m.int64_as_value
assert_equal Google::Protobuf::Int64Value.new(value: 15), m.int64
m.uint32 = Google::Protobuf::UInt32Value.new(value: 50)
assert_equal 50, m.uint32_as_value
assert_equal Google::Protobuf::UInt32Value.new(value: 50), m.uint32
m.uint64 = Google::Protobuf::UInt64Value.new(value: 500)
assert_equal 500, m.uint64_as_value
assert_equal Google::Protobuf::UInt64Value.new(value: 500), m.uint64
m.bool = Google::Protobuf::BoolValue.new(value: false)
assert_equal false, m.bool_as_value
assert_equal Google::Protobuf::BoolValue.new(value: false), m.bool
m.string = Google::Protobuf::StringValue.new(value: 'xy')
assert_equal 'xy', m.string_as_value
assert_equal Google::Protobuf::StringValue.new(value: 'xy'), m.string
m.bytes = Google::Protobuf::BytesValue.new(value: '123')
assert_equal '123', m.bytes_as_value
assert_equal Google::Protobuf::BytesValue.new(value: '123'), m.bytes
m.double = nil
assert_nil m.double
assert_nil m.double_as_value
m.float = nil
assert_nil m.float
assert_nil m.float_as_value
m.int32 = nil
assert_nil m.int32
assert_nil m.int32_as_value
m.int64 = nil
assert_nil m.int64
assert_nil m.int64_as_value
m.uint32 = nil
assert_nil m.uint32
assert_nil m.uint32_as_value
m.uint64 = nil
assert_nil m.uint64
assert_nil m.uint64_as_value
m.bool = nil
assert_nil m.bool
assert_nil m.bool_as_value
m.string = nil
assert_nil m.string
assert_nil m.string_as_value
m.bytes = nil
assert_nil m.bytes
assert_nil m.bytes_as_value
end
def test_wrappers_only
m = proto_module::Wrapper.new(real_string: 'hi', oneof_string: 'there')
assert_raise(NoMethodError) { m.real_string_as_value }
assert_raise(NoMethodError) { m.as_value }
assert_raise(NoMethodError) { m._as_value }
assert_raise(NoMethodError) { m.oneof_string_as_value }
m = proto_module::Wrapper.new
m.string_as_value = 'you'
assert_equal 'you', m.string.value
assert_equal 'you', m.string_as_value
assert_raise(NoMethodError) { m.string_ }
assert_raise(NoMethodError) { m.string_X }
assert_raise(NoMethodError) { m.string_XX }
assert_raise(NoMethodError) { m.string_XXX }
assert_raise(NoMethodError) { m.string_XXXX }
assert_raise(NoMethodError) { m.string_XXXXX }
assert_raise(NoMethodError) { m.string_XXXXXX }
assert_raise(NoMethodError) { m.string_XXXXXXX }
assert_raise(NoMethodError) { m.string_XXXXXXXX }
assert_raise(NoMethodError) { m.string_XXXXXXXXX }
assert_raise(NoMethodError) { m.string_XXXXXXXXXX }
end
def test_converts_time
m = proto_module::TimeMessage.new