aboutsummaryrefslogtreecommitdiffhomepage
path: root/ruby
diff options
context:
space:
mode:
Diffstat (limited to 'ruby')
-rw-r--r--ruby/README.md4
-rw-r--r--ruby/Rakefile15
-rw-r--r--ruby/ext/google/protobuf_c/defs.c32
-rw-r--r--ruby/ext/google/protobuf_c/encode_decode.c88
-rw-r--r--ruby/ext/google/protobuf_c/extconf.rb4
-rw-r--r--ruby/ext/google/protobuf_c/map.c2
-rw-r--r--ruby/ext/google/protobuf_c/message.c10
-rw-r--r--ruby/ext/google/protobuf_c/protobuf.c4
-rw-r--r--ruby/ext/google/protobuf_c/protobuf.h2
-rw-r--r--ruby/ext/google/protobuf_c/repeated_field.c2
-rw-r--r--ruby/ext/google/protobuf_c/storage.c46
-rw-r--r--ruby/google-protobuf.gemspec2
-rw-r--r--ruby/lib/google/protobuf.rb5
-rw-r--r--ruby/lib/google/protobuf/message_exts.rb4
-rw-r--r--ruby/lib/google/protobuf/repeated_field.rb4
-rw-r--r--ruby/lib/google/protobuf/well_known_types.rb6
-rw-r--r--ruby/src/main/java/com/google/protobuf/jruby/RubyMessage.java2
-rw-r--r--ruby/tests/basic.rb56
-rw-r--r--ruby/tests/encode_decode_test.rb87
-rw-r--r--ruby/tests/generated_code.proto13
-rw-r--r--ruby/tests/generated_code_test.rb2
-rw-r--r--ruby/tests/repeated_field_test.rb15
-rw-r--r--ruby/tests/test_ruby_package.proto7
-rw-r--r--ruby/tests/well_known_types_test.rb8
24 files changed, 344 insertions, 76 deletions
diff --git a/ruby/README.md b/ruby/README.md
index f28e05a7..78e86015 100644
--- a/ruby/README.md
+++ b/ruby/README.md
@@ -16,10 +16,6 @@ install it as you would any other gem:
$ gem install [--prerelease] google-protobuf
-The `--pre` flag is necessary if we have not yet made a non-alpha/beta release
-of the Ruby extension; it allows `gem` to consider these "pre-release"
-alpha/beta versions.
-
Once the gem is installed, you may or may not need `protoc`. If you write your
message type descriptions directly in the Ruby DSL, you do not need it.
However, if you wish to generate the Ruby DSL from a `.proto` file, you will
diff --git a/ruby/Rakefile b/ruby/Rakefile
index e30a75a3..013bc99a 100644
--- a/ruby/Rakefile
+++ b/ruby/Rakefile
@@ -52,6 +52,12 @@ if RUBY_PLATFORM == "java"
end
else
Rake::ExtensionTask.new("protobuf_c", spec) do |ext|
+ unless RUBY_PLATFORM =~ /darwin/
+ # TODO: also set "no_native to true" for mac if possible. As is,
+ # "no_native" can only be set if the RUBY_PLATFORM doing
+ # cross-compilation is contained in the "ext.cross_platform" array.
+ ext.no_native = true
+ end
ext.ext_dir = "ext/google/protobuf_c"
ext.lib_dir = "lib/google"
ext.cross_compile = true
@@ -64,13 +70,13 @@ else
task 'gem:windows' do
require 'rake_compiler_dock'
- RakeCompilerDock.sh "bundle && IN_DOCKER=true rake cross native gem RUBY_CC_VERSION=2.4.0:2.3.0:2.2.2:2.1.5:2.0.0"
+ RakeCompilerDock.sh "bundle && IN_DOCKER=true rake cross native gem RUBY_CC_VERSION=2.5.0:2.4.0:2.3.0:2.2.2:2.1.6:2.0.0"
end
if RUBY_PLATFORM =~ /darwin/
task 'gem:native' do
system "rake genproto"
- system "rake cross native gem RUBY_CC_VERSION=2.4.0:2.3.0:2.2.2:2.1.5:2.0.0"
+ system "rake cross native gem RUBY_CC_VERSION=2.5.0:2.4.0:2.3.0:2.2.2:2.1.6:2.0.0"
end
else
task 'gem:native' => [:genproto, 'gem:windows']
@@ -81,6 +87,7 @@ end
# Proto for tests.
genproto_output << "tests/generated_code.rb"
genproto_output << "tests/test_import.rb"
+genproto_output << "tests/test_ruby_package.rb"
file "tests/generated_code.rb" => "tests/generated_code.proto" do |file_task|
sh "../src/protoc --ruby_out=. tests/generated_code.proto"
end
@@ -89,6 +96,10 @@ file "tests/test_import.rb" => "tests/test_import.proto" do |file_task|
sh "../src/protoc --ruby_out=. tests/test_import.proto"
end
+file "tests/test_ruby_package.rb" => "tests/test_ruby_package.proto" do |file_task|
+ sh "../src/protoc --ruby_out=. tests/test_ruby_package.proto"
+end
+
task :genproto => genproto_output
task :clean do
diff --git a/ruby/ext/google/protobuf_c/defs.c b/ruby/ext/google/protobuf_c/defs.c
index 34d9663a..9fe04503 100644
--- a/ruby/ext/google/protobuf_c/defs.c
+++ b/ruby/ext/google/protobuf_c/defs.c
@@ -76,7 +76,7 @@ static upb_enumdef* check_enum_notfrozen(const upb_enumdef* def) {
// -----------------------------------------------------------------------------
#define DEFINE_CLASS(name, string_name) \
- VALUE c ## name; \
+ VALUE c ## name = Qnil; \
const rb_data_type_t _ ## name ## _type = { \
string_name, \
{ name ## _mark, name ## _free, NULL }, \
@@ -126,11 +126,11 @@ void DescriptorPool_register(VALUE module) {
rb_define_method(klass, "lookup", DescriptorPool_lookup, 1);
rb_define_singleton_method(klass, "generated_pool",
DescriptorPool_generated_pool, 0);
- cDescriptorPool = klass;
rb_gc_register_address(&cDescriptorPool);
+ cDescriptorPool = klass;
- generated_pool = rb_class_new_instance(0, NULL, klass);
rb_gc_register_address(&generated_pool);
+ generated_pool = rb_class_new_instance(0, NULL, klass);
}
static void add_descriptor_to_pool(DescriptorPool* self,
@@ -299,8 +299,8 @@ void Descriptor_register(VALUE module) {
rb_define_method(klass, "name", Descriptor_name, 0);
rb_define_method(klass, "name=", Descriptor_name_set, 1);
rb_include_module(klass, rb_mEnumerable);
- cDescriptor = klass;
rb_gc_register_address(&cDescriptor);
+ cDescriptor = klass;
}
/*
@@ -518,8 +518,8 @@ void FieldDescriptor_register(VALUE module) {
rb_define_method(klass, "subtype", FieldDescriptor_subtype, 0);
rb_define_method(klass, "get", FieldDescriptor_get, 1);
rb_define_method(klass, "set", FieldDescriptor_set, 2);
- cFieldDescriptor = klass;
rb_gc_register_address(&cFieldDescriptor);
+ cFieldDescriptor = klass;
}
/*
@@ -812,7 +812,7 @@ VALUE FieldDescriptor_submsg_name_set(VALUE _self, VALUE value) {
upb_fielddef* mut_def = check_field_notfrozen(self->fielddef);
const char* str = get_str(value);
if (!upb_fielddef_hassubdef(self->fielddef)) {
- rb_raise(rb_eTypeError, "FieldDescriptor does not have subdef.");
+ rb_raise(cTypeError, "FieldDescriptor does not have subdef.");
}
CHECK_UPB(upb_fielddef_setsubdefname(mut_def, str, &status),
"Error setting submessage name");
@@ -854,7 +854,7 @@ VALUE FieldDescriptor_get(VALUE _self, VALUE msg_rb) {
MessageHeader* msg;
TypedData_Get_Struct(msg_rb, MessageHeader, &Message_type, msg);
if (msg->descriptor->msgdef != upb_fielddef_containingtype(self->fielddef)) {
- rb_raise(rb_eTypeError, "get method called on wrong message type");
+ rb_raise(cTypeError, "get method called on wrong message type");
}
return layout_get(msg->descriptor->layout, Message_data(msg), self->fielddef);
}
@@ -872,7 +872,7 @@ VALUE FieldDescriptor_set(VALUE _self, VALUE msg_rb, VALUE value) {
MessageHeader* msg;
TypedData_Get_Struct(msg_rb, MessageHeader, &Message_type, msg);
if (msg->descriptor->msgdef != upb_fielddef_containingtype(self->fielddef)) {
- rb_raise(rb_eTypeError, "set method called on wrong message type");
+ rb_raise(cTypeError, "set method called on wrong message type");
}
layout_set(msg->descriptor->layout, Message_data(msg), self->fielddef, value);
return Qnil;
@@ -916,8 +916,8 @@ void OneofDescriptor_register(VALUE module) {
rb_define_method(klass, "add_field", OneofDescriptor_add_field, 1);
rb_define_method(klass, "each", OneofDescriptor_each, 0);
rb_include_module(klass, rb_mEnumerable);
- cOneofDescriptor = klass;
rb_gc_register_address(&cOneofDescriptor);
+ cOneofDescriptor = klass;
}
/*
@@ -1037,8 +1037,8 @@ void EnumDescriptor_register(VALUE module) {
rb_define_method(klass, "each", EnumDescriptor_each, 0);
rb_define_method(klass, "enummodule", EnumDescriptor_enummodule, 0);
rb_include_module(klass, rb_mEnumerable);
- cEnumDescriptor = klass;
rb_gc_register_address(&cEnumDescriptor);
+ cEnumDescriptor = klass;
}
/*
@@ -1202,8 +1202,8 @@ void MessageBuilderContext_register(VALUE module) {
rb_define_method(klass, "repeated", MessageBuilderContext_repeated, -1);
rb_define_method(klass, "map", MessageBuilderContext_map, -1);
rb_define_method(klass, "oneof", MessageBuilderContext_oneof, 1);
- cMessageBuilderContext = klass;
rb_gc_register_address(&cMessageBuilderContext);
+ cMessageBuilderContext = klass;
}
/*
@@ -1491,8 +1491,8 @@ void OneofBuilderContext_register(VALUE module) {
rb_define_method(klass, "initialize",
OneofBuilderContext_initialize, 2);
rb_define_method(klass, "optional", OneofBuilderContext_optional, -1);
- cOneofBuilderContext = klass;
rb_gc_register_address(&cOneofBuilderContext);
+ cOneofBuilderContext = klass;
}
/*
@@ -1569,8 +1569,8 @@ void EnumBuilderContext_register(VALUE module) {
rb_define_method(klass, "initialize",
EnumBuilderContext_initialize, 1);
rb_define_method(klass, "value", EnumBuilderContext_value, 2);
- cEnumBuilderContext = klass;
rb_gc_register_address(&cEnumBuilderContext);
+ cEnumBuilderContext = klass;
}
/*
@@ -1645,8 +1645,8 @@ void Builder_register(VALUE module) {
rb_define_method(klass, "add_enum", Builder_add_enum, 1);
rb_define_method(klass, "initialize", Builder_initialize, 0);
rb_define_method(klass, "finalize_to_pool", Builder_finalize_to_pool, 1);
- cBuilder = klass;
rb_gc_register_address(&cBuilder);
+ cBuilder = klass;
}
/*
@@ -1713,7 +1713,7 @@ static void validate_msgdef(const upb_msgdef* msgdef) {
upb_msg_field_next(&it)) {
const upb_fielddef* field = upb_msg_iter_field(&it);
if (upb_fielddef_label(field) == UPB_LABEL_REQUIRED) {
- rb_raise(rb_eTypeError, "Required fields are unsupported in proto3.");
+ rb_raise(cTypeError, "Required fields are unsupported in proto3.");
}
}
}
@@ -1723,7 +1723,7 @@ static void validate_enumdef(const upb_enumdef* enumdef) {
// value.)
const char* lookup = upb_enumdef_iton(enumdef, 0);
if (lookup == NULL) {
- rb_raise(rb_eTypeError,
+ rb_raise(cTypeError,
"Enum definition does not contain a value for '0'.");
}
}
diff --git a/ruby/ext/google/protobuf_c/encode_decode.c b/ruby/ext/google/protobuf_c/encode_decode.c
index d1b6e89e..12080d03 100644
--- a/ruby/ext/google/protobuf_c/encode_decode.c
+++ b/ruby/ext/google/protobuf_c/encode_decode.c
@@ -1305,3 +1305,91 @@ VALUE Message_encode_json(int argc, VALUE* argv, VALUE klass) {
}
}
+static void discard_unknown(VALUE msg_rb, const Descriptor* desc) {
+ MessageHeader* msg;
+ upb_msg_field_iter it;
+
+ TypedData_Get_Struct(msg_rb, MessageHeader, &Message_type, msg);
+
+ stringsink* unknown = msg->unknown_fields;
+ if (unknown != NULL) {
+ stringsink_uninit(unknown);
+ msg->unknown_fields = NULL;
+ }
+
+ for (upb_msg_field_begin(&it, desc->msgdef);
+ !upb_msg_field_done(&it);
+ upb_msg_field_next(&it)) {
+ upb_fielddef *f = upb_msg_iter_field(&it);
+ uint32_t offset =
+ desc->layout->fields[upb_fielddef_index(f)].offset +
+ sizeof(MessageHeader);
+
+ if (upb_fielddef_containingoneof(f)) {
+ uint32_t oneof_case_offset =
+ desc->layout->fields[upb_fielddef_index(f)].case_offset +
+ sizeof(MessageHeader);
+ // For a oneof, check that this field is actually present -- skip all the
+ // below if not.
+ if (DEREF(msg, oneof_case_offset, uint32_t) !=
+ upb_fielddef_number(f)) {
+ continue;
+ }
+ // Otherwise, fall through to the appropriate singular-field handler
+ // below.
+ }
+
+ if (!upb_fielddef_issubmsg(f)) {
+ continue;
+ }
+
+ if (is_map_field(f)) {
+ if (!upb_fielddef_issubmsg(map_field_value(f))) continue;
+ VALUE map = DEREF(msg, offset, VALUE);
+ if (map == Qnil) continue;
+ Map_iter map_it;
+ for (Map_begin(map, &map_it); !Map_done(&map_it); Map_next(&map_it)) {
+ VALUE submsg = Map_iter_value(&map_it);
+ VALUE descriptor = rb_ivar_get(submsg, descriptor_instancevar_interned);
+ const Descriptor* subdesc = ruby_to_Descriptor(descriptor);
+ discard_unknown(submsg, subdesc);
+ }
+ } else if (upb_fielddef_isseq(f)) {
+ VALUE ary = DEREF(msg, offset, VALUE);
+ if (ary == Qnil) continue;
+ int size = NUM2INT(RepeatedField_length(ary));
+ for (int i = 0; i < size; i++) {
+ void* memory = RepeatedField_index_native(ary, i);
+ VALUE submsg = *((VALUE *)memory);
+ VALUE descriptor = rb_ivar_get(submsg, descriptor_instancevar_interned);
+ const Descriptor* subdesc = ruby_to_Descriptor(descriptor);
+ discard_unknown(submsg, subdesc);
+ }
+ } else {
+ VALUE submsg = DEREF(msg, offset, VALUE);
+ if (submsg == Qnil) continue;
+ VALUE descriptor = rb_ivar_get(submsg, descriptor_instancevar_interned);
+ const Descriptor* subdesc = ruby_to_Descriptor(descriptor);
+ discard_unknown(submsg, subdesc);
+ }
+ }
+}
+
+/*
+ * call-seq:
+ * Google::Protobuf.discard_unknown(msg)
+ *
+ * Discard unknown fields in the given message object and recursively discard
+ * unknown fields in submessages.
+ */
+VALUE Google_Protobuf_discard_unknown(VALUE self, VALUE msg_rb) {
+ VALUE klass = CLASS_OF(msg_rb);
+ VALUE descriptor = rb_ivar_get(klass, descriptor_instancevar_interned);
+ Descriptor* desc = ruby_to_Descriptor(descriptor);
+ if (klass == cRepeatedField || klass == cMap) {
+ rb_raise(rb_eArgError, "Expected proto msg for discard unknown.");
+ } else {
+ discard_unknown(msg_rb, desc);
+ }
+ return Qnil;
+}
diff --git a/ruby/ext/google/protobuf_c/extconf.rb b/ruby/ext/google/protobuf_c/extconf.rb
index 0886e607..cc097e11 100644
--- a/ruby/ext/google/protobuf_c/extconf.rb
+++ b/ruby/ext/google/protobuf_c/extconf.rb
@@ -2,7 +2,9 @@
require 'mkmf'
-$CFLAGS += " -std=c99 -O3 -DNDEBUG"
+unless RUBY_PLATFORM =~ /mswin|mingw/
+ $CFLAGS += " -std=c99 -O3 -DNDEBUG"
+end
if RUBY_PLATFORM =~ /linux/
diff --git a/ruby/ext/google/protobuf_c/map.c b/ruby/ext/google/protobuf_c/map.c
index 26e22dc7..8c2f6424 100644
--- a/ruby/ext/google/protobuf_c/map.c
+++ b/ruby/ext/google/protobuf_c/map.c
@@ -825,8 +825,8 @@ VALUE Map_iter_value(Map_iter* iter) {
void Map_register(VALUE module) {
VALUE klass = rb_define_class_under(module, "Map", rb_cObject);
rb_define_alloc_func(klass, Map_alloc);
- cMap = klass;
rb_gc_register_address(&cMap);
+ cMap = klass;
rb_define_method(klass, "initialize", Map_init, -1);
rb_define_method(klass, "each", Map_each, 0);
diff --git a/ruby/ext/google/protobuf_c/message.c b/ruby/ext/google/protobuf_c/message.c
index bc73d48c..721c1112 100644
--- a/ruby/ext/google/protobuf_c/message.c
+++ b/ruby/ext/google/protobuf_c/message.c
@@ -256,6 +256,10 @@ int Message_initialize_kwarg(VALUE key, VALUE val, VALUE _self) {
"Unknown field name '%s' in initialization map entry.", name);
}
+ if (TYPE(val) == T_NIL) {
+ return 0;
+ }
+
if (is_map_field(f)) {
VALUE map;
@@ -540,9 +544,9 @@ VALUE build_class_from_descriptor(Descriptor* desc) {
get_def_obj(desc->msgdef));
rb_define_alloc_func(klass, Message_alloc);
rb_require("google/protobuf/message_exts");
- rb_include_module(klass, rb_eval_string("Google::Protobuf::MessageExts"));
+ rb_include_module(klass, rb_eval_string("::Google::Protobuf::MessageExts"));
rb_extend_object(
- klass, rb_eval_string("Google::Protobuf::MessageExts::ClassMethods"));
+ klass, rb_eval_string("::Google::Protobuf::MessageExts::ClassMethods"));
rb_define_method(klass, "method_missing",
Message_method_missing, -1);
@@ -631,7 +635,7 @@ VALUE build_module_from_enumdesc(EnumDescriptor* enumdesc) {
const char* name = upb_enum_iter_name(&it);
int32_t value = upb_enum_iter_number(&it);
if (name[0] < 'A' || name[0] > 'Z') {
- rb_raise(rb_eTypeError,
+ rb_raise(cTypeError,
"Enum value '%s' does not start with an uppercase letter "
"as is required for Ruby constants.",
name);
diff --git a/ruby/ext/google/protobuf_c/protobuf.c b/ruby/ext/google/protobuf_c/protobuf.c
index c7750c44..fe6bb406 100644
--- a/ruby/ext/google/protobuf_c/protobuf.c
+++ b/ruby/ext/google/protobuf_c/protobuf.c
@@ -41,6 +41,7 @@ VALUE upb_def_to_ruby_obj_map;
VALUE cError;
VALUE cParseError;
+VALUE cTypeError;
void add_def_obj(const void* def, VALUE value) {
rb_hash_aset(upb_def_to_ruby_obj_map, ULL2NUM((intptr_t)def), value);
@@ -102,7 +103,10 @@ void Init_protobuf_c() {
cError = rb_const_get(protobuf, rb_intern("Error"));
cParseError = rb_const_get(protobuf, rb_intern("ParseError"));
+ cTypeError = rb_const_get(protobuf, rb_intern("TypeError"));
+ rb_define_singleton_method(protobuf, "discard_unknown",
+ Google_Protobuf_discard_unknown, 1);
rb_define_singleton_method(protobuf, "deep_copy",
Google_Protobuf_deep_copy, 1);
diff --git a/ruby/ext/google/protobuf_c/protobuf.h b/ruby/ext/google/protobuf_c/protobuf.h
index 1291ac59..3e5c0520 100644
--- a/ruby/ext/google/protobuf_c/protobuf.h
+++ b/ruby/ext/google/protobuf_c/protobuf.h
@@ -161,6 +161,7 @@ extern VALUE cBuilder;
extern VALUE cError;
extern VALUE cParseError;
+extern VALUE cTypeError;
// We forward-declare all of the Ruby method implementations here because we
// sometimes call the methods directly across .c files, rather than going
@@ -515,6 +516,7 @@ VALUE Message_encode(VALUE klass, VALUE msg_rb);
VALUE Message_decode_json(VALUE klass, VALUE data);
VALUE Message_encode_json(int argc, VALUE* argv, VALUE klass);
+VALUE Google_Protobuf_discard_unknown(VALUE self, VALUE msg_rb);
VALUE Google_Protobuf_deep_copy(VALUE self, VALUE obj);
VALUE build_module_from_enumdesc(EnumDescriptor* enumdef);
diff --git a/ruby/ext/google/protobuf_c/repeated_field.c b/ruby/ext/google/protobuf_c/repeated_field.c
index 1c651c19..c6620ee6 100644
--- a/ruby/ext/google/protobuf_c/repeated_field.c
+++ b/ruby/ext/google/protobuf_c/repeated_field.c
@@ -626,8 +626,8 @@ void RepeatedField_register(VALUE module) {
VALUE klass = rb_define_class_under(
module, "RepeatedField", rb_cObject);
rb_define_alloc_func(klass, RepeatedField_alloc);
- cRepeatedField = klass;
rb_gc_register_address(&cRepeatedField);
+ cRepeatedField = klass;
rb_define_method(klass, "initialize",
RepeatedField_init, -1);
diff --git a/ruby/ext/google/protobuf_c/storage.c b/ruby/ext/google/protobuf_c/storage.c
index 24064dfd..5d0ac976 100644
--- a/ruby/ext/google/protobuf_c/storage.c
+++ b/ruby/ext/google/protobuf_c/storage.c
@@ -96,7 +96,7 @@ static bool is_ruby_num(VALUE value) {
void native_slot_check_int_range_precision(upb_fieldtype_t type, VALUE val) {
if (!is_ruby_num(val)) {
- rb_raise(rb_eTypeError, "Expected number type for integral field.");
+ rb_raise(cTypeError, "Expected number type for integral field.");
}
// NUM2{INT,UINT,LL,ULL} macros do the appropriate range checks on upper
@@ -153,13 +153,13 @@ void native_slot_set_value_and_case(upb_fieldtype_t type, VALUE type_class,
switch (type) {
case UPB_TYPE_FLOAT:
if (!is_ruby_num(value)) {
- rb_raise(rb_eTypeError, "Expected number type for float field.");
+ rb_raise(cTypeError, "Expected number type for float field.");
}
DEREF(memory, float) = NUM2DBL(value);
break;
case UPB_TYPE_DOUBLE:
if (!is_ruby_num(value)) {
- rb_raise(rb_eTypeError, "Expected number type for double field.");
+ rb_raise(cTypeError, "Expected number type for double field.");
}
DEREF(memory, double) = NUM2DBL(value);
break;
@@ -170,7 +170,7 @@ void native_slot_set_value_and_case(upb_fieldtype_t type, VALUE type_class,
} else if (value == Qfalse) {
val = 0;
} else {
- rb_raise(rb_eTypeError, "Invalid argument for boolean field.");
+ rb_raise(cTypeError, "Invalid argument for boolean field.");
}
DEREF(memory, int8_t) = val;
break;
@@ -179,7 +179,7 @@ void native_slot_set_value_and_case(upb_fieldtype_t type, VALUE type_class,
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.");
+ rb_raise(cTypeError, "Invalid argument for string field.");
}
DEREF(memory, VALUE) = native_slot_encode_and_freeze_string(type, value);
@@ -187,7 +187,7 @@ void native_slot_set_value_and_case(upb_fieldtype_t type, VALUE type_class,
case UPB_TYPE_BYTES: {
if (CLASS_OF(value) != rb_cString) {
- rb_raise(rb_eTypeError, "Invalid argument for string field.");
+ rb_raise(cTypeError, "Invalid argument for string field.");
}
DEREF(memory, VALUE) = native_slot_encode_and_freeze_string(type, value);
@@ -197,7 +197,7 @@ void native_slot_set_value_and_case(upb_fieldtype_t type, VALUE type_class,
if (CLASS_OF(value) == CLASS_OF(Qnil)) {
value = Qnil;
} else if (CLASS_OF(value) != type_class) {
- rb_raise(rb_eTypeError,
+ rb_raise(cTypeError,
"Invalid type %s to assign to submessage field.",
rb_class2name(CLASS_OF(value)));
}
@@ -209,7 +209,7 @@ void native_slot_set_value_and_case(upb_fieldtype_t type, VALUE type_class,
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(cTypeError,
"Expected number or symbol type for enum field.");
}
if (TYPE(value) == T_SYMBOL) {
@@ -598,20 +598,28 @@ static void check_repeated_field_type(VALUE val, const upb_fielddef* field) {
if (!RB_TYPE_P(val, T_DATA) || !RTYPEDDATA_P(val) ||
RTYPEDDATA_TYPE(val) != &RepeatedField_type) {
- rb_raise(rb_eTypeError, "Expected repeated field array");
+ rb_raise(cTypeError, "Expected repeated field array");
}
self = ruby_to_RepeatedField(val);
if (self->field_type != upb_fielddef_type(field)) {
- rb_raise(rb_eTypeError, "Repeated field array has wrong element type");
+ rb_raise(cTypeError, "Repeated field array has wrong element type");
}
- if (self->field_type == UPB_TYPE_MESSAGE ||
- self->field_type == UPB_TYPE_ENUM) {
+ if (self->field_type == UPB_TYPE_MESSAGE) {
if (self->field_type_class !=
- get_def_obj(upb_fielddef_subdef(field))) {
- rb_raise(rb_eTypeError,
- "Repeated field array has wrong message/enum class");
+ Descriptor_msgclass(get_def_obj(upb_fielddef_subdef(field)))) {
+ rb_raise(cTypeError,
+ "Repeated field array has wrong message class");
+ }
+ }
+
+
+ if (self->field_type == UPB_TYPE_ENUM) {
+ if (self->field_type_class !=
+ EnumDescriptor_enummodule(get_def_obj(upb_fielddef_subdef(field)))) {
+ rb_raise(cTypeError,
+ "Repeated field array has wrong enum class");
}
}
}
@@ -623,21 +631,21 @@ static void check_map_field_type(VALUE val, const upb_fielddef* field) {
if (!RB_TYPE_P(val, T_DATA) || !RTYPEDDATA_P(val) ||
RTYPEDDATA_TYPE(val) != &Map_type) {
- rb_raise(rb_eTypeError, "Expected Map instance");
+ rb_raise(cTypeError, "Expected Map instance");
}
self = ruby_to_Map(val);
if (self->key_type != upb_fielddef_type(key_field)) {
- rb_raise(rb_eTypeError, "Map key type does not match field's key type");
+ rb_raise(cTypeError, "Map key type does not match field's key type");
}
if (self->value_type != upb_fielddef_type(value_field)) {
- rb_raise(rb_eTypeError, "Map value type does not match field's value type");
+ rb_raise(cTypeError, "Map value type does not match field's value type");
}
if (upb_fielddef_type(value_field) == UPB_TYPE_MESSAGE ||
upb_fielddef_type(value_field) == UPB_TYPE_ENUM) {
if (self->value_type_class !=
get_def_obj(upb_fielddef_subdef(value_field))) {
- rb_raise(rb_eTypeError,
+ rb_raise(cTypeError,
"Map value type has wrong message/enum class");
}
}
diff --git a/ruby/google-protobuf.gemspec b/ruby/google-protobuf.gemspec
index 96606f06..cc7625d4 100644
--- a/ruby/google-protobuf.gemspec
+++ b/ruby/google-protobuf.gemspec
@@ -1,6 +1,6 @@
Gem::Specification.new do |s|
s.name = "google-protobuf"
- s.version = "3.4.1.1"
+ s.version = "3.6.0"
s.licenses = ["BSD-3-Clause"]
s.summary = "Protocol Buffers"
s.description = "Protocol Buffers are Google's data interchange format."
diff --git a/ruby/lib/google/protobuf.rb b/ruby/lib/google/protobuf.rb
index 9b8d8231..e20a584e 100644
--- a/ruby/lib/google/protobuf.rb
+++ b/ruby/lib/google/protobuf.rb
@@ -37,6 +37,7 @@ module Google
module Protobuf
class Error < StandardError; end
class ParseError < Error; end
+ class TypeError < ::TypeError; end
end
end
@@ -60,8 +61,8 @@ module Google
msg.to_proto
end
- def self.encode_json(msg)
- msg.to_json
+ def self.encode_json(msg, options = {})
+ msg.to_json(options)
end
def self.decode(klass, proto)
diff --git a/ruby/lib/google/protobuf/message_exts.rb b/ruby/lib/google/protobuf/message_exts.rb
index e10266ba..f432f89f 100644
--- a/ruby/lib/google/protobuf/message_exts.rb
+++ b/ruby/lib/google/protobuf/message_exts.rb
@@ -40,8 +40,8 @@ module Google
module ClassMethods
end
- def to_json
- self.class.encode_json(self)
+ def to_json(options = {})
+ self.class.encode_json(self, options)
end
def to_proto
diff --git a/ruby/lib/google/protobuf/repeated_field.rb b/ruby/lib/google/protobuf/repeated_field.rb
index 11d6c2eb..2dae1e65 100644
--- a/ruby/lib/google/protobuf/repeated_field.rb
+++ b/ruby/lib/google/protobuf/repeated_field.rb
@@ -150,12 +150,12 @@ module Google
end
- %w(delete delete_at delete_if shift slice! unshift).each do |method_name|
+ %w(delete delete_at shift slice! unshift).each do |method_name|
define_array_wrapper_method(method_name)
end
- %w(collect! compact! fill flatten! insert reverse!
+ %w(collect! compact! delete_if fill flatten! insert reverse!
rotate! select! shuffle! sort! sort_by! uniq!).each do |method_name|
define_array_wrapper_with_result_method(method_name)
end
diff --git a/ruby/lib/google/protobuf/well_known_types.rb b/ruby/lib/google/protobuf/well_known_types.rb
index e85fac56..2ee65bc2 100644
--- a/ruby/lib/google/protobuf/well_known_types.rb
+++ b/ruby/lib/google/protobuf/well_known_types.rb
@@ -39,6 +39,12 @@ module Google
module Protobuf
Any.class_eval do
+ def self.pack(msg, type_url_prefix='type.googleapis.com/')
+ any = self.new
+ any.pack(msg, type_url_prefix)
+ any
+ end
+
def pack(msg, type_url_prefix='type.googleapis.com/')
if type_url_prefix.empty? or type_url_prefix[-1] != '/' then
self.type_url = "#{type_url_prefix}/#{msg.class.descriptor.name}"
diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyMessage.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyMessage.java
index 07558fbc..c3a0d81c 100644
--- a/ruby/src/main/java/com/google/protobuf/jruby/RubyMessage.java
+++ b/ruby/src/main/java/com/google/protobuf/jruby/RubyMessage.java
@@ -86,6 +86,8 @@ public class RubyMessage extends RubyObject {
throw runtime.newTypeError("Expected string or symbols as hash keys in initialization map.");
final Descriptors.FieldDescriptor fieldDescriptor = findField(context, key);
+ if (value.isNil()) return;
+
if (Utils.isMapEntry(fieldDescriptor)) {
if (!(value instanceof RubyHash))
throw runtime.newArgumentError("Expected Hash object as initializer value for map field '" + key.asJavaString() + "'.");
diff --git a/ruby/tests/basic.rb b/ruby/tests/basic.rb
index ad34d53d..0a5c5fb5 100644
--- a/ruby/tests/basic.rb
+++ b/ruby/tests/basic.rb
@@ -212,6 +212,15 @@ module BasicTest
assert_equal ['foo', 'bar'], m.repeated_string
end
+ def test_ctor_nil_args
+ m = TestMessage.new(:optional_enum => nil, :optional_int32 => nil, :optional_string => nil, :optional_msg => nil)
+
+ assert_equal :Default, m.optional_enum
+ assert_equal 0, m.optional_int32
+ assert_equal "", m.optional_string
+ assert_nil m.optional_msg
+ end
+
def test_embeddedmsg_hash_init
m = TestEmbeddedMessageParent.new(:child_msg => {sub_child: {optional_int32: 1}},
:number => 2,
@@ -283,31 +292,36 @@ module BasicTest
def test_type_errors
m = TestMessage.new
- assert_raise TypeError do
+ e = assert_raise Google::Protobuf::TypeError do
m.optional_int32 = "hello"
end
- assert_raise TypeError do
+
+ # Google::Protobuf::TypeError should inherit from TypeError for backwards compatibility
+ # TODO: This can be removed when we can safely migrate to Google::Protobuf::TypeError
+ assert_true e.is_a?(::TypeError)
+
+ assert_raise Google::Protobuf::TypeError do
m.optional_string = 42
end
- assert_raise TypeError do
+ assert_raise Google::Protobuf::TypeError do
m.optional_string = nil
end
- assert_raise TypeError do
+ assert_raise Google::Protobuf::TypeError do
m.optional_bool = 42
end
- assert_raise TypeError do
+ assert_raise Google::Protobuf::TypeError do
m.optional_msg = TestMessage.new # expects TestMessage2
end
- assert_raise TypeError do
+ assert_raise Google::Protobuf::TypeError do
m.repeated_int32 = [] # needs RepeatedField
end
- assert_raise TypeError do
+ assert_raise Google::Protobuf::TypeError do
m.repeated_int32.push "hello"
end
- assert_raise TypeError do
+ assert_raise Google::Protobuf::TypeError do
m.repeated_msg.push TestMessage.new
end
end
@@ -373,7 +387,7 @@ module BasicTest
assert l.pop == 9
assert l == [5, 2, 3, 4, 7, 8]
- assert_raise TypeError do
+ assert_raise Google::Protobuf::TypeError do
m = TestMessage.new
l.push m
end
@@ -423,10 +437,10 @@ module BasicTest
l = Google::Protobuf::RepeatedField.new(:message, TestMessage)
l.push TestMessage.new
assert l.count == 1
- assert_raise TypeError do
+ assert_raise Google::Protobuf::TypeError do
l.push TestMessage2.new
end
- assert_raise TypeError do
+ assert_raise Google::Protobuf::TypeError do
l.push 42
end
@@ -575,7 +589,7 @@ module BasicTest
assert_raise RangeError do
m[0x8000_0000] = 1
end
- assert_raise TypeError do
+ assert_raise Google::Protobuf::TypeError do
m["asdf"] = 1
end
@@ -584,7 +598,7 @@ module BasicTest
assert_raise RangeError do
m[0x1_0000_0000_0000_0000] = 1
end
- assert_raise TypeError do
+ assert_raise Google::Protobuf::TypeError do
m["asdf"] = 1
end
@@ -609,10 +623,10 @@ module BasicTest
m = Google::Protobuf::Map.new(:bool, :int32)
m[true] = 1
m[false] = 2
- assert_raise TypeError do
+ assert_raise Google::Protobuf::TypeError do
m[1] = 1
end
- assert_raise TypeError do
+ assert_raise Google::Protobuf::TypeError do
m["asdf"] = 1
end
@@ -639,7 +653,7 @@ module BasicTest
def test_map_msg_enum_valuetypes
m = Google::Protobuf::Map.new(:string, :message, TestMessage)
m["asdf"] = TestMessage.new
- assert_raise TypeError do
+ assert_raise Google::Protobuf::TypeError do
m["jkl;"] = TestMessage2.new
end
@@ -706,17 +720,17 @@ module BasicTest
m.map_string_msg.delete("c")
assert m.map_string_msg == { "a" => TestMessage2.new(:foo => 1) }
- assert_raise TypeError do
+ assert_raise Google::Protobuf::TypeError do
m.map_string_msg["e"] = TestMessage.new # wrong value type
end
# ensure nothing was added by the above
assert m.map_string_msg == { "a" => TestMessage2.new(:foo => 1) }
m.map_string_int32 = Google::Protobuf::Map.new(:string, :int32)
- assert_raise TypeError do
+ assert_raise Google::Protobuf::TypeError do
m.map_string_int32 = Google::Protobuf::Map.new(:string, :int64)
end
- assert_raise TypeError do
+ assert_raise Google::Protobuf::TypeError do
m.map_string_int32 = {}
end
@@ -1015,7 +1029,7 @@ module BasicTest
def test_def_errors
s = Google::Protobuf::DescriptorPool.new
- assert_raise TypeError do
+ assert_raise Google::Protobuf::TypeError do
s.build do
# enum with no default (integer value 0)
add_enum "MyEnum" do
@@ -1023,7 +1037,7 @@ module BasicTest
end
end
end
- assert_raise TypeError do
+ assert_raise Google::Protobuf::TypeError do
s.build do
# message with required field (unsupported in proto3)
add_message "MyMessage" do
diff --git a/ruby/tests/encode_decode_test.rb b/ruby/tests/encode_decode_test.rb
new file mode 100644
index 00000000..2bd9b98b
--- /dev/null
+++ b/ruby/tests/encode_decode_test.rb
@@ -0,0 +1,87 @@
+#!/usr/bin/ruby
+
+# generated_code.rb is in the same directory as this test.
+$LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__)))
+
+require 'generated_code_pb'
+require 'test/unit'
+
+def hex2bin(s)
+ s.scan(/../).map { |x| x.hex.chr }.join
+end
+
+class EncodeDecodeTest < Test::Unit::TestCase
+ def test_discard_unknown
+ # Test discard unknown in message.
+ unknown_msg = A::B::C::TestUnknown.new(:unknown_field => 1)
+ from = A::B::C::TestUnknown.encode(unknown_msg)
+ m = A::B::C::TestMessage.decode(from)
+ Google::Protobuf.discard_unknown(m)
+ to = A::B::C::TestMessage.encode(m)
+ assert_equal '', to
+
+ # Test discard unknown for singular message field.
+ unknown_msg = A::B::C::TestUnknown.new(
+ :optional_unknown =>
+ A::B::C::TestUnknown.new(:unknown_field => 1))
+ from = A::B::C::TestUnknown.encode(unknown_msg)
+ m = A::B::C::TestMessage.decode(from)
+ Google::Protobuf.discard_unknown(m)
+ to = A::B::C::TestMessage.encode(m.optional_msg)
+ assert_equal '', to
+
+ # Test discard unknown for repeated message field.
+ unknown_msg = A::B::C::TestUnknown.new(
+ :repeated_unknown =>
+ [A::B::C::TestUnknown.new(:unknown_field => 1)])
+ from = A::B::C::TestUnknown.encode(unknown_msg)
+ m = A::B::C::TestMessage.decode(from)
+ Google::Protobuf.discard_unknown(m)
+ to = A::B::C::TestMessage.encode(m.repeated_msg[0])
+ assert_equal '', to
+
+ # Test discard unknown for map value message field.
+ unknown_msg = A::B::C::TestUnknown.new(
+ :map_unknown =>
+ {"" => A::B::C::TestUnknown.new(:unknown_field => 1)})
+ from = A::B::C::TestUnknown.encode(unknown_msg)
+ m = A::B::C::TestMessage.decode(from)
+ Google::Protobuf.discard_unknown(m)
+ to = A::B::C::TestMessage.encode(m.map_string_msg[''])
+ assert_equal '', to
+
+ # Test discard unknown for oneof message field.
+ unknown_msg = A::B::C::TestUnknown.new(
+ :oneof_unknown =>
+ A::B::C::TestUnknown.new(:unknown_field => 1))
+ from = A::B::C::TestUnknown.encode(unknown_msg)
+ m = A::B::C::TestMessage.decode(from)
+ Google::Protobuf.discard_unknown(m)
+ to = A::B::C::TestMessage.encode(m.oneof_msg)
+ assert_equal '', to
+ end
+
+ def test_encode_json
+ msg = A::B::C::TestMessage.new({ optional_int32: 22 })
+ json = msg.to_json
+
+ to = A::B::C::TestMessage.decode_json(json)
+ assert_equal to.optional_int32, 22
+
+ msg = A::B::C::TestMessage.new({ optional_int32: 22 })
+ json = msg.to_json({ preserve_proto_fieldnames: true })
+
+ assert_match 'optional_int32', json
+
+ to = A::B::C::TestMessage.decode_json(json)
+ assert_equal 22, to.optional_int32
+
+ msg = A::B::C::TestMessage.new({ optional_int32: 22 })
+ json = A::B::C::TestMessage.encode_json(
+ msg,
+ { preserve_proto_fieldnames: true, emit_defaults: true }
+ )
+
+ assert_match 'optional_int32', json
+ end
+end
diff --git a/ruby/tests/generated_code.proto b/ruby/tests/generated_code.proto
index 62fd83ed..3b934bd6 100644
--- a/ruby/tests/generated_code.proto
+++ b/ruby/tests/generated_code.proto
@@ -57,6 +57,9 @@ message TestMessage {
}
NestedMessage nested_message = 80;
+
+ // Reserved for non-existing field test.
+ // int32 non_exist = 89;
}
enum TestEnum {
@@ -65,3 +68,13 @@ enum TestEnum {
B = 2;
C = 3;
}
+
+message TestUnknown {
+ TestUnknown optional_unknown = 11;
+ repeated TestUnknown repeated_unknown = 31;
+ oneof my_oneof {
+ TestUnknown oneof_unknown = 51;
+ }
+ map<string, TestUnknown> map_unknown = 67;
+ int32 unknown_field = 89;
+}
diff --git a/ruby/tests/generated_code_test.rb b/ruby/tests/generated_code_test.rb
index b92b0462..431d681b 100644
--- a/ruby/tests/generated_code_test.rb
+++ b/ruby/tests/generated_code_test.rb
@@ -5,6 +5,7 @@ $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__)))
require 'generated_code_pb'
require 'test_import_pb'
+require 'test_ruby_package_pb'
require 'test/unit'
class GeneratedCodeTest < Test::Unit::TestCase
@@ -15,5 +16,6 @@ class GeneratedCodeTest < Test::Unit::TestCase
# aspect of the extension (basic.rb is for that).
m = A::B::C::TestMessage.new()
m2 = FooBar::TestImportedMessage.new()
+ m3 = A::B::TestRubyPackageMessage.new()
end
end
diff --git a/ruby/tests/repeated_field_test.rb b/ruby/tests/repeated_field_test.rb
index 25727b7b..61ac4afd 100644
--- a/ruby/tests/repeated_field_test.rb
+++ b/ruby/tests/repeated_field_test.rb
@@ -126,6 +126,12 @@ class RepeatedFieldTest < Test::Unit::TestCase
assert_equal false, m.repeated_string.empty?
end
+ def test_reassign
+ m = TestMessage.new
+ m.repeated_msg = Google::Protobuf::RepeatedField.new(:message, TestMessage2, [TestMessage2.new(:foo => 1)])
+ assert_equal m.repeated_msg.first, TestMessage2.new(:foo => 1)
+ end
+
def test_array_accessor
m = TestMessage.new
reference_arr = %w(foo bar baz)
@@ -363,6 +369,15 @@ class RepeatedFieldTest < Test::Unit::TestCase
end
end
+ def test_delete_if
+ m = TestMessage.new
+ reference_arr = %w(foo bar baz)
+ m.repeated_string += reference_arr.clone
+ check_self_modifying_method(m.repeated_string, reference_arr) do |arr|
+ arr.delete_if { |v| v == "bar" }
+ end
+ end
+
def test_fill
m = TestMessage.new
reference_arr = %w(foo bar baz)
diff --git a/ruby/tests/test_ruby_package.proto b/ruby/tests/test_ruby_package.proto
new file mode 100644
index 00000000..b8725620
--- /dev/null
+++ b/ruby/tests/test_ruby_package.proto
@@ -0,0 +1,7 @@
+syntax = "proto3";
+
+package foo_bar;
+
+option ruby_package = "A.B";
+
+message TestRubyPackageMessage {}
diff --git a/ruby/tests/well_known_types_test.rb b/ruby/tests/well_known_types_test.rb
index 3ce659a6..f35f7b13 100644
--- a/ruby/tests/well_known_types_test.rb
+++ b/ruby/tests/well_known_types_test.rb
@@ -126,11 +126,17 @@ class TestWellKnownTypes < Test::Unit::TestCase
end
def test_any
- any = Google::Protobuf::Any.new
ts = Google::Protobuf::Timestamp.new(seconds: 12345, nanos: 6789)
+
+ any = Google::Protobuf::Any.new
any.pack(ts)
assert any.is(Google::Protobuf::Timestamp)
assert_equal ts, any.unpack(Google::Protobuf::Timestamp)
+
+ any = Google::Protobuf::Any.pack(ts)
+
+ assert any.is(Google::Protobuf::Timestamp)
+ assert_equal ts, any.unpack(Google::Protobuf::Timestamp)
end
end