From e2debef5d8cd084946bd14fecabda5c328382114 Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Wed, 14 Jan 2015 18:02:27 -0800 Subject: Ruby extension: added oneof accessor. --- ruby/ext/google/protobuf_c/encode_decode.c | 3 +- ruby/ext/google/protobuf_c/message.c | 45 ++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) (limited to 'ruby/ext') diff --git a/ruby/ext/google/protobuf_c/encode_decode.c b/ruby/ext/google/protobuf_c/encode_decode.c index 5e53b694..0db86209 100644 --- a/ruby/ext/google/protobuf_c/encode_decode.c +++ b/ruby/ext/google/protobuf_c/encode_decode.c @@ -77,7 +77,8 @@ static const void *newoneofhandlerdata(upb_handlers *h, // we don't expose these numbers to the user, so the only requirement is that // we have some unique ID for each union case/possibility. The field tag // numbers are already present and are easy to use so there's no reason to - // create a separate ID space. + // create a separate ID space. In addition, using the field tag number here + // lets us easily look up the field in the oneof accessor. hd->oneof_case_num = upb_fielddef_number(f); if (upb_fielddef_type(f) == UPB_TYPE_MESSAGE) { hd->md = upb_fielddef_msgsubdef(f); diff --git a/ruby/ext/google/protobuf_c/message.c b/ruby/ext/google/protobuf_c/message.c index ee8881d4..d0b09014 100644 --- a/ruby/ext/google/protobuf_c/message.c +++ b/ruby/ext/google/protobuf_c/message.c @@ -70,6 +70,36 @@ VALUE Message_alloc(VALUE klass) { return ret; } +static VALUE which_oneof_field(MessageHeader* self, const upb_oneofdef* o) { + // If no fields in the oneof, always nil. + if (upb_oneofdef_numfields(o) == 0) { + return Qnil; + } + // Grab the first field in the oneof so we can get its layout info to find the + // oneof_case field. + upb_oneof_iter it; + upb_oneof_begin(&it, o); + assert(!upb_oneof_done(&it)); + const upb_fielddef* first_field = upb_oneof_iter_field(&it); + assert(upb_fielddef_containingoneof(first_field) != NULL); + + size_t case_ofs = + self->descriptor->layout-> + fields[upb_fielddef_index(first_field)].case_offset; + uint32_t oneof_case = *((uint32_t*)(Message_data(self) + case_ofs)); + + // oneof_case == 0 indicates no field set. + if (oneof_case == 0) { + return Qnil; + } + + // oneof_case is a field index, so find that field. + const upb_fielddef* f = upb_oneofdef_itof(o, oneof_case); + assert(f != NULL); + + return ID2SYM(rb_intern(upb_fielddef_name(f))); +} + /* * call-seq: * Message.method_missing(*args) @@ -82,6 +112,10 @@ VALUE Message_alloc(VALUE klass) { * * msg.foo = 42 * puts msg.foo + * + * This method also provides read-only accessors for oneofs. If a oneof exists + * with name 'my_oneof', then msg.my_oneof will return a Ruby symbol equal to + * the name of the field in that oneof that is currently set, or nil if none. */ VALUE Message_method_missing(int argc, VALUE* argv, VALUE _self) { MessageHeader* self; @@ -104,6 +138,17 @@ VALUE Message_method_missing(int argc, VALUE* argv, VALUE _self) { name_len--; } + // Check for a oneof name first. + const upb_oneofdef* o = upb_msgdef_ntoo(self->descriptor->msgdef, + name, name_len); + if (o != NULL) { + if (setter) { + rb_raise(rb_eRuntimeError, "Oneof accessors are read-only."); + } + return which_oneof_field(self, o); + } + + // Otherwise, check for a field with that name. const upb_fielddef* f = upb_msgdef_ntof(self->descriptor->msgdef, name, name_len); -- cgit v1.2.3