Add wrapper type helpers for Ruby (#5739)
* add wrapper type helpers
* add check for _as_value suffix
diff --git a/ruby/.gitignore b/ruby/.gitignore
index bd8745d..6533098 100644
--- a/ruby/.gitignore
+++ b/ruby/.gitignore
@@ -6,3 +6,4 @@
target/
pkg/
tmp/
+tests/google/
\ No newline at end of file
diff --git a/ruby/Rakefile b/ruby/Rakefile
index 140f5e5..ad70e31 100644
--- a/ruby/Rakefile
+++ b/ruby/Rakefile
@@ -93,6 +93,7 @@
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 @@
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
diff --git a/ruby/ext/google/protobuf_c/message.c b/ruby/ext/google/protobuf_c/message.c
index 5537b38..850ab0e 100644
--- a/ruby/ext/google/protobuf_c/message.c
+++ b/ruby/ext/google/protobuf_c/message.c
@@ -119,9 +119,37 @@
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 @@
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 @@
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 @@
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");
diff --git a/ruby/tests/basic_test.proto b/ruby/tests/basic_test.proto
index 045714b..3a5a532 100644
--- a/ruby/tests/basic_test.proto
+++ b/ruby/tests/basic_test.proto
@@ -2,6 +2,7 @@
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 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;
diff --git a/ruby/tests/basic_test_proto2.proto b/ruby/tests/basic_test_proto2.proto
index d3a3795..e54ed31 100644
--- a/ruby/tests/basic_test_proto2.proto
+++ b/ruby/tests/basic_test_proto2.proto
@@ -2,6 +2,7 @@
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 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;
diff --git a/ruby/tests/common_tests.rb b/ruby/tests/common_tests.rb
index 300c816..2945003 100644
--- a/ruby/tests/common_tests.rb
+++ b/ruby/tests/common_tests.rb
@@ -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 @@
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