Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for descriptor options in ruby interface #12828

Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
100 changes: 99 additions & 1 deletion ruby/ext/google/protobuf_c/defs.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ static VALUE get_enumdef_obj(VALUE descriptor_pool, const upb_EnumDef* def);
static VALUE get_fielddef_obj(VALUE descriptor_pool, const upb_FieldDef* def);
static VALUE get_filedef_obj(VALUE descriptor_pool, const upb_FileDef* def);
static VALUE get_oneofdef_obj(VALUE descriptor_pool, const upb_OneofDef* def);
static VALUE get_serialized_options_obj(const char* serialized, size_t size, upb_Arena* arena);

// A distinct object that is not accessible from Ruby. We use this as a
// constructor argument to enforce that certain objects cannot be created from
Expand Down Expand Up @@ -397,6 +398,22 @@ static VALUE Descriptor_msgclass(VALUE _self) {
return self->klass;
}

/*
* call-seq:
* Descriptor.serialized_options => options
*
* Returns a binary string containing the serialized options for this message.
*/
static VALUE Descriptor_serialized_options(VALUE _self) {
Descriptor* self = ruby_to_Descriptor(_self);
const google_protobuf_MessageOptions* opts = upb_MessageDef_Options(self->msgdef);
upb_Arena* arena = upb_Arena_New();
size_t size;
char* serialized = google_protobuf_MessageOptions_serialize(opts, arena, &size);

return get_serialized_options_obj(serialized, size, arena);
}

static void Descriptor_register(VALUE module) {
VALUE klass = rb_define_class_under(module, "Descriptor", rb_cObject);
rb_define_alloc_func(klass, Descriptor_alloc);
Expand All @@ -408,6 +425,7 @@ static void Descriptor_register(VALUE module) {
rb_define_method(klass, "msgclass", Descriptor_msgclass, 0);
rb_define_method(klass, "name", Descriptor_name, 0);
rb_define_method(klass, "file_descriptor", Descriptor_file_descriptor, 0);
rb_define_private_method(klass, "serialized_options", Descriptor_serialized_options, 0);
rb_include_module(klass, rb_mEnumerable);
rb_gc_register_address(&cDescriptor);
cDescriptor = klass;
Expand Down Expand Up @@ -507,12 +525,29 @@ static VALUE FileDescriptor_syntax(VALUE _self) {
}
}

/*
* call-seq:
* FileDescriptor.serialized_options => options
*
* Returns a binary string containing the serialized options for this message.
*/
static VALUE FileDescriptor_serialized_options(VALUE _self) {
FileDescriptor* self = ruby_to_FileDescriptor(_self);
const google_protobuf_FileOptions* opts = upb_FileDef_Options(self->filedef);
upb_Arena* arena = upb_Arena_New();
size_t size;
char* serialized = google_protobuf_FileOptions_serialize(opts, arena, &size);

return get_serialized_options_obj(serialized, size, arena);
}

static void FileDescriptor_register(VALUE module) {
VALUE klass = rb_define_class_under(module, "FileDescriptor", rb_cObject);
rb_define_alloc_func(klass, FileDescriptor_alloc);
rb_define_method(klass, "initialize", FileDescriptor_initialize, 3);
rb_define_method(klass, "name", FileDescriptor_name, 0);
rb_define_method(klass, "syntax", FileDescriptor_syntax, 0);
rb_define_private_method(klass, "serialized_options", FileDescriptor_serialized_options, 0);
rb_gc_register_address(&cFileDescriptor);
cFileDescriptor = klass;
}
Expand Down Expand Up @@ -563,7 +598,7 @@ static VALUE FieldDescriptor_alloc(VALUE klass) {

/*
* call-seq:
* EnumDescriptor.new(c_only_cookie, pool, ptr) => EnumDescriptor
* FieldDescriptor.new(c_only_cookie, pool, ptr) => FieldDescriptor
*
* Creates a descriptor wrapper object. May only be called from C.
*/
Expand Down Expand Up @@ -864,6 +899,22 @@ static VALUE FieldDescriptor_set(VALUE _self, VALUE msg_rb, VALUE value) {
return Qnil;
}

/*
* call-seq:
* FieldDescriptor.serialized_options => options
*
* Returns a binary string containing the serialized options for this message.
*/
static VALUE FieldDescriptor_serialized_options(VALUE _self) {
FieldDescriptor* self = ruby_to_FieldDescriptor(_self);
const google_protobuf_FieldOptions* opts = upb_FieldDef_Options(self->fielddef);
upb_Arena* arena = upb_Arena_New();
size_t size;
char* serialized = google_protobuf_FieldOptions_serialize(opts, arena, &size);

return get_serialized_options_obj(serialized, size, arena);
}

static void FieldDescriptor_register(VALUE module) {
VALUE klass = rb_define_class_under(module, "FieldDescriptor", rb_cObject);
rb_define_alloc_func(klass, FieldDescriptor_alloc);
Expand All @@ -880,6 +931,7 @@ static void FieldDescriptor_register(VALUE module) {
rb_define_method(klass, "clear", FieldDescriptor_clear, 1);
rb_define_method(klass, "get", FieldDescriptor_get, 1);
rb_define_method(klass, "set", FieldDescriptor_set, 2);
rb_define_private_method(klass, "serialized_options", FieldDescriptor_serialized_options, 0);
rb_gc_register_address(&cFieldDescriptor);
cFieldDescriptor = klass;
}
Expand Down Expand Up @@ -979,12 +1031,29 @@ static VALUE OneofDescriptor_each(VALUE _self) {
return Qnil;
}

/*
* call-seq:
* OneofDescriptor.serialized_options => options
*
* Returns a binary string containing the serialized options for this message.
*/
static VALUE OneOfDescriptor_serialized_options(VALUE _self) {
OneofDescriptor* self = ruby_to_OneofDescriptor(_self);
const google_protobuf_OneofOptions* opts = upb_OneofDef_Options(self->oneofdef);
upb_Arena* arena = upb_Arena_New();
size_t size;
char* serialized = google_protobuf_OneofOptions_serialize(opts, arena, &size);

return get_serialized_options_obj(serialized, size, arena);
}

static void OneofDescriptor_register(VALUE module) {
VALUE klass = rb_define_class_under(module, "OneofDescriptor", rb_cObject);
rb_define_alloc_func(klass, OneofDescriptor_alloc);
rb_define_method(klass, "initialize", OneofDescriptor_initialize, 3);
rb_define_method(klass, "name", OneofDescriptor_name, 0);
rb_define_method(klass, "each", OneofDescriptor_each, 0);
rb_define_private_method(klass, "serialized_options", OneOfDescriptor_serialized_options, 0);
rb_include_module(klass, rb_mEnumerable);
rb_gc_register_address(&cOneofDescriptor);
cOneofDescriptor = klass;
Expand Down Expand Up @@ -1153,6 +1222,22 @@ static VALUE EnumDescriptor_enummodule(VALUE _self) {
return self->module;
}

/*
* call-seq:
* EnumDescriptor.serialized_options => options
*
* Returns a binary string containing the serialized options for this message.
*/
static VALUE EnumDescriptor_serialized_options(VALUE _self) {
EnumDescriptor* self = ruby_to_EnumDescriptor(_self);
const google_protobuf_EnumOptions* opts = upb_EnumDef_Options(self->enumdef);
upb_Arena* arena = upb_Arena_New();
size_t size;
char* serialized = google_protobuf_EnumOptions_serialize(opts, arena, &size);

return get_serialized_options_obj(serialized, size, arena);
}

static void EnumDescriptor_register(VALUE module) {
VALUE klass = rb_define_class_under(module, "EnumDescriptor", rb_cObject);
rb_define_alloc_func(klass, EnumDescriptor_alloc);
Expand All @@ -1163,6 +1248,7 @@ static void EnumDescriptor_register(VALUE module) {
rb_define_method(klass, "each", EnumDescriptor_each, 0);
rb_define_method(klass, "enummodule", EnumDescriptor_enummodule, 0);
rb_define_method(klass, "file_descriptor", EnumDescriptor_file_descriptor, 0);
rb_define_private_method(klass, "serialized_options", EnumDescriptor_serialized_options, 0);
rb_include_module(klass, rb_mEnumerable);
rb_gc_register_address(&cEnumDescriptor);
cEnumDescriptor = klass;
Expand Down Expand Up @@ -1209,6 +1295,18 @@ static VALUE get_oneofdef_obj(VALUE descriptor_pool, const upb_OneofDef* def) {
return get_def_obj(descriptor_pool, def, cOneofDescriptor);
}

static VALUE get_serialized_options_obj(const char* serialized, size_t size, upb_Arena* arena) {
if (serialized) {
VALUE ret = rb_str_new(serialized, size);
rb_enc_associate(ret, rb_ascii8bit_encoding());
upb_Arena_Free(arena);
return ret;
} else {
upb_Arena_Free(arena);
rb_raise(rb_eRuntimeError, "Error encoding");
}
}

// -----------------------------------------------------------------------------
// Shared functions
// -----------------------------------------------------------------------------
Expand Down
28 changes: 28 additions & 0 deletions ruby/ext/google/protobuf_c/message.c
Original file line number Diff line number Diff line change
Expand Up @@ -905,6 +905,33 @@ static VALUE Message_freeze(VALUE _self) {
return _self;
}

/*
* call-seq:
* Message.deep_freeze => self
*
* Deeep freezes the message object recursively.
*/
static VALUE Message_deep_freeze(VALUE _self) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's call it internal_deep_freeze just so it's unambiguous that it's intended to be internal-only.

It doesn't make it harder to call, but if someone does call it they will know that it's an API that could change.

Message* self = ruby_to_Message(_self);
if (!RB_OBJ_FROZEN(_self)) {
Message_freeze(_self);

int n = upb_MessageDef_FieldCount(self->msgdef);
for (int i = 0; i < n; i++) {
const upb_FieldDef* f = upb_MessageDef_Field(self->msgdef, i);
VALUE field = Message_getfield(_self, f);

if (upb_FieldDef_IsMap(f) || upb_FieldDef_IsRepeated(f)) {
rb_funcall(field, rb_intern("freeze"), 0);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Repeated fields and maps can contain messages, so we will need them to support deep_freeze also.

} else if (upb_FieldDef_IsSubMessage(f)) {
Message_deep_freeze(field);
}
}
}

return _self;
}

/*
* call-seq:
* Message.[](index) => value
Expand Down Expand Up @@ -1407,6 +1434,7 @@ static void Message_define_class(VALUE klass) {
rb_define_method(klass, "==", Message_eq, 1);
rb_define_method(klass, "eql?", Message_eq, 1);
rb_define_method(klass, "freeze", Message_freeze, 0);
rb_define_private_method(klass, "deep_freeze", Message_deep_freeze, 0);
rb_define_method(klass, "hash", Message_hash, 0);
rb_define_method(klass, "to_h", Message_to_h, 0);
rb_define_method(klass, "inspect", Message_inspect, 0);
Expand Down
54 changes: 54 additions & 0 deletions ruby/lib/google/protobuf/descriptor_dsl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,10 @@ def value(name, number)
end
end

def self.decode_options(klass, serialized_options)
options = klass.decode(serialized_options)
options.send(:deep_freeze)
end
end

# Re-open the class (the rest of the class is implemented in C)
Expand All @@ -461,5 +465,55 @@ def build(&block)
builder.build
end
end

# Re-open the class (the rest of the class is implemented in C)
class Descriptor
def options
@options ||= Google::Protobuf::Internal.decode_options(
Google::Protobuf::MessageOptions,
serialized_options
)
end
end

# Re-open the class (the rest of the class is implemented in C)
class FileDescriptor
def options
@options ||= Google::Protobuf::Internal.decode_options(
Google::Protobuf::FileOptions,
serialized_options
)
end
end

# Re-open the class (the rest of the class is implemented in C)
class FieldDescriptor
def options
@options ||= Google::Protobuf::Internal.decode_options(
Google::Protobuf::FieldOptions,
serialized_options
)
end
end

# Re-open the class (the rest of the class is implemented in C)
class EnumDescriptor
def options
@options ||= Google::Protobuf::Internal.decode_options(
Google::Protobuf::EnumOptions,
serialized_options
)
end
end

# Re-open the class (the rest of the class is implemented in C)
class OneofDescriptor
def options
@options ||= Google::Protobuf::Internal.decode_options(
Google::Protobuf::OneofOptions,
serialized_options
)
end
end
end
end
37 changes: 37 additions & 0 deletions ruby/tests/basic.rb
Original file line number Diff line number Diff line change
Expand Up @@ -711,6 +711,43 @@ def test_map_fields_respond_to? # regression test for issue 9202
msg.map_string_int32_as_value = :boom
end
end

def test_file_descriptor_options
file_descriptor = TestMessage.descriptor.file_descriptor

assert_equal file_descriptor.options.class, Google::Protobuf::FileOptions
assert file_descriptor.options.deprecated
end

def test_descriptor_options
descriptor = TestDeprecatedMessage.descriptor

assert_equal descriptor.options.class, Google::Protobuf::MessageOptions
assert descriptor.options.deprecated
end

def test_enum_descriptor_options
enum_descriptor = TestDeprecatedEnum.descriptor

assert_equal enum_descriptor.options.class, Google::Protobuf::EnumOptions
assert enum_descriptor.options.deprecated
end

def test_oneof_descriptor_options
descriptor = TestDeprecatedMessage.descriptor
oneof_descriptor = descriptor.lookup_oneof("test_deprecated_message_oneof")

assert_equal oneof_descriptor.options.class, Google::Protobuf::OneofOptions
end

def test_options_deep_freeze
descriptor = TestDeprecatedMessage.descriptor

assert_raise FrozenError do
descriptor.options.uninterpreted_option.push \
Google::Protobuf::UninterpretedOption.new
end
end
end

def test_oneof_fields_respond_to? # regression test for issue 9202
Expand Down
20 changes: 20 additions & 0 deletions ruby/tests/basic_test.proto
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import "google/protobuf/duration.proto";
import "google/protobuf/struct.proto";
import "test_import_proto2.proto";

option deprecated = true;

message Foo {
Bar bar = 1;
repeated Baz baz = 2;
Expand Down Expand Up @@ -68,6 +70,17 @@ message TestMessage2 {
optional int32 foo = 1;
}

message TestDeprecatedMessage {
option deprecated = true;

optional int32 foo = 1;

oneof test_deprecated_message_oneof {
string a = 2;
int32 b = 3;
}
}

enum TestEnum {
Default = 0;
A = 1;
Expand All @@ -76,6 +89,13 @@ enum TestEnum {
v0 = 4;
}

enum TestDeprecatedEnum {
option deprecated = true;

DefaultA = 0;
AA = 1 [deprecated = true];
}

message TestEmbeddedMessageParent {
TestEmbeddedMessageChild child_msg = 1;
int32 number = 2;
Expand Down