Allow initializing a chain of protos using only a hash

pull/3627/head
Zachary Anker 7 years ago
parent 06aa8dc9e7
commit 87714836e3
  1. 2
      ruby/compatibility_tests/v3.0.0/tests/basic.rb
  2. 34
      ruby/ext/google/protobuf_c/message.c
  3. 13
      ruby/ext/google/protobuf_c/storage.c
  4. 41
      ruby/src/main/java/com/google/protobuf/jruby/RubyMessage.java
  5. 49
      ruby/tests/basic.rb

@ -600,7 +600,7 @@ module BasicTest
assert_raise RangeError do assert_raise RangeError do
m["z"] = :Z m["z"] = :Z
end end
assert_raise TypeError do assert_raise RangeError do
m["z"] = "z" m["z"] = "z"
end end
end end

@ -217,20 +217,32 @@ VALUE Message_respond_to_missing(int argc, VALUE* argv, VALUE _self) {
return Qtrue; return Qtrue;
} }
VALUE create_submsg_from_hash(const upb_fielddef *f, VALUE hash) {
const upb_def *d = upb_fielddef_subdef(f);
assert(d != NULL);
VALUE descriptor = get_def_obj(d);
VALUE msgclass = rb_funcall(descriptor, rb_intern("msgclass"), 0, NULL);
VALUE args[1] = { hash };
return rb_class_new_instance(1, args, msgclass);
}
int Message_initialize_kwarg(VALUE key, VALUE val, VALUE _self) { int Message_initialize_kwarg(VALUE key, VALUE val, VALUE _self) {
MessageHeader* self; MessageHeader* self;
VALUE method_str;
char *name; char *name;
const upb_fielddef* f; const upb_fielddef* f;
TypedData_Get_Struct(_self, MessageHeader, &Message_type, self); TypedData_Get_Struct(_self, MessageHeader, &Message_type, self);
if (!SYMBOL_P(key)) { if (TYPE(key) == T_STRING) {
name = RSTRING_PTR(key);
} else if (TYPE(key) == T_SYMBOL) {
name = RSTRING_PTR(rb_id2str(SYM2ID(key)));
} else {
rb_raise(rb_eArgError, rb_raise(rb_eArgError,
"Expected symbols as hash keys in initialization map."); "Expected string or symbols as hash keys when initializing proto from hash.");
} }
method_str = rb_id2str(SYM2ID(key));
name = RSTRING_PTR(method_str);
f = upb_msgdef_ntofz(self->descriptor->msgdef, name); f = upb_msgdef_ntofz(self->descriptor->msgdef, name);
if (f == NULL) { if (f == NULL) {
rb_raise(rb_eArgError, rb_raise(rb_eArgError,
@ -248,6 +260,7 @@ int Message_initialize_kwarg(VALUE key, VALUE val, VALUE _self) {
Map_merge_into_self(map, val); Map_merge_into_self(map, val);
} else if (upb_fielddef_label(f) == UPB_LABEL_REPEATED) { } else if (upb_fielddef_label(f) == UPB_LABEL_REPEATED) {
VALUE ary; VALUE ary;
VALUE entry;
if (TYPE(val) != T_ARRAY) { if (TYPE(val) != T_ARRAY) {
rb_raise(rb_eArgError, rb_raise(rb_eArgError,
@ -255,9 +268,18 @@ int Message_initialize_kwarg(VALUE key, VALUE val, VALUE _self) {
} }
ary = layout_get(self->descriptor->layout, Message_data(self), f); ary = layout_get(self->descriptor->layout, Message_data(self), f);
for (int i = 0; i < RARRAY_LEN(val); i++) { for (int i = 0; i < RARRAY_LEN(val); i++) {
RepeatedField_push(ary, rb_ary_entry(val, i)); entry = rb_ary_entry(val, i);
if (TYPE(entry) == T_HASH && upb_fielddef_issubmsg(f)) {
entry = create_submsg_from_hash(f, entry);
}
RepeatedField_push(ary, entry);
} }
} else { } else {
if (TYPE(val) == T_HASH && upb_fielddef_issubmsg(f)) {
val = create_submsg_from_hash(f, val);
}
layout_set(self->descriptor->layout, Message_data(self), f, val); layout_set(self->descriptor->layout, Message_data(self), f, val);
} }
return 0; return 0;

@ -176,6 +176,15 @@ void native_slot_set_value_and_case(upb_fieldtype_t type, VALUE type_class,
break; break;
} }
case UPB_TYPE_STRING: case UPB_TYPE_STRING:
if (CLASS_OF(value) == rb_cSymbol) {
value = rb_funcall(value, rb_intern("to_s"), 0, NULL);
} else if (CLASS_OF(value) != rb_cString) {
rb_raise(rb_eTypeError, "Invalid argument for string field.");
}
DEREF(memory, VALUE) = native_slot_encode_and_freeze_string(type, value);
break;
case UPB_TYPE_BYTES: { case UPB_TYPE_BYTES: {
if (CLASS_OF(value) != rb_cString) { if (CLASS_OF(value) != rb_cString) {
rb_raise(rb_eTypeError, "Invalid argument for string field."); rb_raise(rb_eTypeError, "Invalid argument for string field.");
@ -197,7 +206,9 @@ void native_slot_set_value_and_case(upb_fieldtype_t type, VALUE type_class,
} }
case UPB_TYPE_ENUM: { case UPB_TYPE_ENUM: {
int32_t int_val = 0; int32_t int_val = 0;
if (!is_ruby_num(value) && TYPE(value) != T_SYMBOL) { if (TYPE(value) == T_STRING) {
value = rb_funcall(value, rb_intern("to_sym"), 0, NULL);
} else if (!is_ruby_num(value) && TYPE(value) != T_SYMBOL) {
rb_raise(rb_eTypeError, rb_raise(rb_eTypeError,
"Expected number or symbol type for enum field."); "Expected number or symbol type for enum field.");
} }

@ -82,8 +82,8 @@ public class RubyMessage extends RubyObject {
hash.visitAll(new RubyHash.Visitor() { hash.visitAll(new RubyHash.Visitor() {
@Override @Override
public void visit(IRubyObject key, IRubyObject value) { public void visit(IRubyObject key, IRubyObject value) {
if (!(key instanceof RubySymbol)) if (!(key instanceof RubySymbol) && !(key instanceof RubyString))
throw runtime.newTypeError("Expected symbols as hash keys in initialization map."); throw runtime.newTypeError("Expected string or symbols as hash keys in initialization map.");
final Descriptors.FieldDescriptor fieldDescriptor = findField(context, key); final Descriptors.FieldDescriptor fieldDescriptor = findField(context, key);
if (Utils.isMapEntry(fieldDescriptor)) { if (Utils.isMapEntry(fieldDescriptor)) {
@ -103,9 +103,15 @@ public class RubyMessage extends RubyObject {
if (oneof != null) { if (oneof != null) {
oneofCases.put(oneof, fieldDescriptor); oneofCases.put(oneof, fieldDescriptor);
} }
fields.put(fieldDescriptor, value);
if (value instanceof RubyHash && fieldDescriptor.getType() == Descriptors.FieldDescriptor.Type.MESSAGE) {
RubyDescriptor descriptor = (RubyDescriptor) getDescriptorForField(context, fieldDescriptor);
RubyClass typeClass = (RubyClass) descriptor.msgclass(context);
value = (IRubyObject) typeClass.newInstance(context, value, Block.NULL_BLOCK);
} }
fields.put(fieldDescriptor, value);
}
} }
}); });
} }
@ -518,19 +524,12 @@ public class RubyMessage extends RubyObject {
val = value.isTrue(); val = value.isTrue();
break; break;
case BYTES: case BYTES:
case STRING:
Utils.validateStringEncoding(context, fieldDescriptor.getType(), value); Utils.validateStringEncoding(context, fieldDescriptor.getType(), value);
RubyString str = (RubyString) value; val = ByteString.copyFrom(((RubyString) value).getBytes());
switch (fieldDescriptor.getType()) {
case BYTES:
val = ByteString.copyFrom(str.getBytes());
break; break;
case STRING: case STRING:
val = str.asJavaString(); Utils.validateStringEncoding(context, fieldDescriptor.getType(), value);
break; val = ((RubyString) value).asJavaString();
default:
break;
}
break; break;
case MESSAGE: case MESSAGE:
RubyClass typeClass = (RubyClass) ((RubyDescriptor) getDescriptorForField(context, fieldDescriptor)).msgclass(context); RubyClass typeClass = (RubyClass) ((RubyDescriptor) getDescriptorForField(context, fieldDescriptor)).msgclass(context);
@ -543,7 +542,7 @@ public class RubyMessage extends RubyObject {
if (Utils.isRubyNum(value)) { if (Utils.isRubyNum(value)) {
val = enumDescriptor.findValueByNumberCreatingIfUnknown(RubyNumeric.num2int(value)); val = enumDescriptor.findValueByNumberCreatingIfUnknown(RubyNumeric.num2int(value));
} else if (value instanceof RubySymbol) { } else if (value instanceof RubySymbol || value instanceof RubyString) {
val = enumDescriptor.findValueByName(value.asJavaString()); val = enumDescriptor.findValueByName(value.asJavaString());
} else { } else {
throw runtime.newTypeError("Expected number or symbol type for enum field."); throw runtime.newTypeError("Expected number or symbol type for enum field.");
@ -741,8 +740,20 @@ public class RubyMessage extends RubyObject {
Descriptors.FieldDescriptor fieldDescriptor, IRubyObject value) { Descriptors.FieldDescriptor fieldDescriptor, IRubyObject value) {
RubyArray arr = value.convertToArray(); RubyArray arr = value.convertToArray();
RubyRepeatedField repeatedField = repeatedFieldForFieldDescriptor(context, fieldDescriptor); RubyRepeatedField repeatedField = repeatedFieldForFieldDescriptor(context, fieldDescriptor);
RubyClass typeClass = null;
if (fieldDescriptor.getType() == Descriptors.FieldDescriptor.Type.MESSAGE) {
RubyDescriptor descriptor = (RubyDescriptor) getDescriptorForField(context, fieldDescriptor);
typeClass = (RubyClass) descriptor.msgclass(context);
}
for (int i = 0; i < arr.size(); i++) { for (int i = 0; i < arr.size(); i++) {
repeatedField.push(context, arr.eltInternal(i)); IRubyObject row = arr.eltInternal(i);
if (row instanceof RubyHash && typeClass != null) {
row = (IRubyObject) typeClass.newInstance(context, row, Block.NULL_BLOCK);
}
repeatedField.push(context, row);
} }
return repeatedField; return repeatedField;
} }

@ -51,6 +51,17 @@ module BasicTest
optional :foo, :int32, 1 optional :foo, :int32, 1
end end
add_message "TestEmbeddedMessageParent" do
optional :child_msg, :message, 1, "TestEmbeddedMessageChild"
optional :number, :int32, 2
repeated :repeated_msg, :message, 3, "TestEmbeddedMessageChild"
repeated :repeated_number, :int32, 4
end
add_message "TestEmbeddedMessageChild" do
optional :sub_child, :message, 1, "TestMessage"
end
add_message "Recursive1" do add_message "Recursive1" do
optional :foo, :message, 1, "Recursive2" optional :foo, :message, 1, "Recursive2"
end end
@ -113,6 +124,8 @@ module BasicTest
Baz = pool.lookup("Baz").msgclass Baz = pool.lookup("Baz").msgclass
TestMessage = pool.lookup("TestMessage").msgclass TestMessage = pool.lookup("TestMessage").msgclass
TestMessage2 = pool.lookup("TestMessage2").msgclass TestMessage2 = pool.lookup("TestMessage2").msgclass
TestEmbeddedMessageParent = pool.lookup("TestEmbeddedMessageParent").msgclass
TestEmbeddedMessageChild = pool.lookup("TestEmbeddedMessageChild").msgclass
Recursive1 = pool.lookup("Recursive1").msgclass Recursive1 = pool.lookup("Recursive1").msgclass
Recursive2 = pool.lookup("Recursive2").msgclass Recursive2 = pool.lookup("Recursive2").msgclass
TestEnum = pool.lookup("TestEnum").enummodule TestEnum = pool.lookup("TestEnum").enummodule
@ -161,12 +174,18 @@ module BasicTest
m.optional_double = 0.5 m.optional_double = 0.5
m.optional_string = "hello" m.optional_string = "hello"
assert m.optional_string == "hello" assert m.optional_string == "hello"
m.optional_string = :hello
assert m.optional_string == "hello"
m.optional_bytes = "world".encode!('ASCII-8BIT') m.optional_bytes = "world".encode!('ASCII-8BIT')
assert m.optional_bytes == "world" assert m.optional_bytes == "world"
m.optional_msg = TestMessage2.new(:foo => 42) m.optional_msg = TestMessage2.new(:foo => 42)
assert m.optional_msg == TestMessage2.new(:foo => 42) assert m.optional_msg == TestMessage2.new(:foo => 42)
m.optional_msg = nil m.optional_msg = nil
assert m.optional_msg == nil assert m.optional_msg == nil
m.optional_enum = :C
assert m.optional_enum == :C
m.optional_enum = 'C'
assert m.optional_enum == :C
end end
def test_ctor_args def test_ctor_args
@ -183,6 +202,34 @@ module BasicTest
assert m.repeated_string[2] == "world" assert m.repeated_string[2] == "world"
end end
def test_ctor_string_symbol_args
m = TestMessage.new(:optional_enum => 'C', :repeated_enum => ['A', 'B'])
assert_equal :C, m.optional_enum
assert_equal [:A, :B], m.repeated_enum
m = TestMessage.new(:optional_string => :foo, :repeated_string => [:foo, :bar])
assert_equal 'foo', m.optional_string
assert_equal ['foo', 'bar'], m.repeated_string
end
def test_embeddedmsg_hash_init
m = TestEmbeddedMessageParent.new(:child_msg => {sub_child: {optional_int32: 1}},
:number => 2,
:repeated_msg => [{sub_child: {optional_int32: 3}}],
:repeated_number => [10, 20, 30])
assert_equal 2, m.number
assert_equal [10, 20, 30], m.repeated_number
assert_not_nil m.child_msg
assert_not_nil m.child_msg.sub_child
assert_equal m.child_msg.sub_child.optional_int32, 1
assert_not_nil m.repeated_msg
assert_equal 1, m.repeated_msg.length
assert_equal 3, m.repeated_msg.first.sub_child.optional_int32
end
def test_inspect def test_inspect
m = TestMessage.new(:optional_int32 => -42, m = TestMessage.new(:optional_int32 => -42,
:optional_enum => :A, :optional_enum => :A,
@ -614,7 +661,7 @@ module BasicTest
assert_raise RangeError do assert_raise RangeError do
m["z"] = :Z m["z"] = :Z
end end
assert_raise TypeError do assert_raise RangeError do
m["z"] = "z" m["z"] = "z"
end end
end end

Loading…
Cancel
Save