message.c 19.7 KB
Newer Older
Chris Fallin's avatar
Chris Fallin committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
// Protocol Buffers - Google's data interchange format
// Copyright 2014 Google Inc.  All rights reserved.
// https://developers.google.com/protocol-buffers/
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//     * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include "protobuf.h"

// -----------------------------------------------------------------------------
// Class/module creation from msgdefs and enumdefs, respectively.
// -----------------------------------------------------------------------------

void* Message_data(void* msg) {
  return ((uint8_t *)msg) + sizeof(MessageHeader);
}

void Message_mark(void* _self) {
  MessageHeader* self = (MessageHeader *)_self;
  layout_mark(self->descriptor->layout, Message_data(self));
}

void Message_free(void* self) {
  xfree(self);
}

rb_data_type_t Message_type = {
  "Message",
  { Message_mark, Message_free, NULL },
};

VALUE Message_alloc(VALUE klass) {
56
  VALUE descriptor = rb_ivar_get(klass, descriptor_instancevar_interned);
Chris Fallin's avatar
Chris Fallin committed
57 58 59
  Descriptor* desc = ruby_to_Descriptor(descriptor);
  MessageHeader* msg = (MessageHeader*)ALLOC_N(
      uint8_t, sizeof(MessageHeader) + desc->layout->size);
60 61
  VALUE ret;

Chris Fallin's avatar
Chris Fallin committed
62 63 64 65
  memset(Message_data(msg), 0, desc->layout->size);

  // We wrap first so that everything in the message object is GC-rooted in case
  // a collection happens during object creation in layout_init().
66
  ret = TypedData_Wrap_Struct(klass, &Message_type, msg);
Chris Fallin's avatar
Chris Fallin committed
67
  msg->descriptor = desc;
68
  rb_ivar_set(ret, descriptor_instancevar_interned, descriptor);
Chris Fallin's avatar
Chris Fallin committed
69 70 71 72 73 74

  layout_init(desc->layout, Message_data(msg));

  return ret;
}

75
static VALUE which_oneof_field(MessageHeader* self, const upb_oneofdef* o) {
76 77 78 79 80 81
  upb_oneof_iter it;
  size_t case_ofs;
  uint32_t oneof_case;
  const upb_fielddef* first_field;
  const upb_fielddef* f;

82 83 84 85 86 87 88 89
  // 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_begin(&it, o);
  assert(!upb_oneof_done(&it));
90
  first_field = upb_oneof_iter_field(&it);
91 92
  assert(upb_fielddef_containingoneof(first_field) != NULL);

93
  case_ofs =
94 95
      self->descriptor->layout->
      fields[upb_fielddef_index(first_field)].case_offset;
96
  oneof_case = *((uint32_t*)((char*)Message_data(self) + case_ofs));
97

98
  if (oneof_case == ONEOF_CASE_NONE) {
99 100 101 102
    return Qnil;
  }

  // oneof_case is a field index, so find that field.
103
  f = upb_oneofdef_itof(o, oneof_case);
104 105 106 107 108
  assert(f != NULL);

  return ID2SYM(rb_intern(upb_fielddef_name(f)));
}

Chris Fallin's avatar
Chris Fallin committed
109 110 111 112 113 114 115 116 117 118 119 120
/*
 * call-seq:
 *     Message.method_missing(*args)
 *
 * Provides accessors and setters for message fields according to their field
 * names. For any field whose name does not conflict with a built-in method, an
 * accessor is provided with the same name as the field, and a setter is
 * provided with the name of the field plus the '=' suffix. Thus, given a
 * message instance 'msg' with field 'foo', the following code is valid:
 *
 *     msg.foo = 42
 *     puts msg.foo
121 122 123 124
 *
 * 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.
Chris Fallin's avatar
Chris Fallin committed
125 126 127
 */
VALUE Message_method_missing(int argc, VALUE* argv, VALUE _self) {
  MessageHeader* self;
128 129 130 131 132 133 134
  VALUE method_name, method_str;
  char* name;
  size_t name_len;
  bool setter;
  const upb_oneofdef* o;
  const upb_fielddef* f;

Chris Fallin's avatar
Chris Fallin committed
135 136 137 138
  TypedData_Get_Struct(_self, MessageHeader, &Message_type, self);
  if (argc < 1) {
    rb_raise(rb_eArgError, "Expected method name as first argument.");
  }
139
  method_name = argv[0];
Chris Fallin's avatar
Chris Fallin committed
140 141 142
  if (!SYMBOL_P(method_name)) {
    rb_raise(rb_eArgError, "Expected symbol as method name.");
  }
143 144 145 146
  method_str = rb_id2str(SYM2ID(method_name));
  name = RSTRING_PTR(method_str);
  name_len = RSTRING_LEN(method_str);
  setter = false;
Chris Fallin's avatar
Chris Fallin committed
147 148 149 150 151 152 153

  // Setters have names that end in '='.
  if (name[name_len - 1] == '=') {
    setter = true;
    name_len--;
  }

154 155 156 157 158 159
  // See if this name corresponds to either a oneof or field in this message.
  if (!upb_msgdef_lookupname(self->descriptor->msgdef, name, name_len, &f,
                             &o)) {
    return rb_call_super(argc, argv);
  }

160
  if (o != NULL) {
161
    // This is a oneof -- return which field inside the oneof is set.
162 163 164 165
    if (setter) {
      rb_raise(rb_eRuntimeError, "Oneof accessors are read-only.");
    }
    return which_oneof_field(self, o);
Chris Fallin's avatar
Chris Fallin committed
166
  } else {
167 168 169 170 171 172 173 174 175 176 177
    // This is a field -- get or set the field's value.
    assert(f);
    if (setter) {
      if (argc < 2) {
        rb_raise(rb_eArgError, "No value provided to setter.");
      }
      layout_set(self->descriptor->layout, Message_data(self), f, argv[1]);
      return Qnil;
    } else {
      return layout_get(self->descriptor->layout, Message_data(self), f);
    }
Chris Fallin's avatar
Chris Fallin committed
178 179 180
  }
}

181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
VALUE Message_respond_to_missing(int argc, VALUE* argv, VALUE _self) {
  MessageHeader* self;
  VALUE method_name, method_str;
  char* name;
  size_t name_len;
  bool setter;
  const upb_oneofdef* o;
  const upb_fielddef* f;

  TypedData_Get_Struct(_self, MessageHeader, &Message_type, self);
  if (argc < 1) {
    rb_raise(rb_eArgError, "Expected method name as first argument.");
  }
  method_name = argv[0];
  if (!SYMBOL_P(method_name)) {
    rb_raise(rb_eArgError, "Expected symbol as method name.");
  }
  method_str = rb_id2str(SYM2ID(method_name));
  name = RSTRING_PTR(method_str);
  name_len = RSTRING_LEN(method_str);
  setter = false;

  // Setters have names that end in '='.
  if (name[name_len - 1] == '=') {
    setter = true;
    name_len--;
  }

  // See if this name corresponds to either a oneof or field in this message.
  if (!upb_msgdef_lookupname(self->descriptor->msgdef, name, name_len, &f,
                             &o)) {
    return rb_call_super(argc, argv);
  }
  if (o != NULL) {
    return setter ? Qfalse : Qtrue;
  }
  return Qtrue;
}

Chris Fallin's avatar
Chris Fallin committed
220 221
int Message_initialize_kwarg(VALUE key, VALUE val, VALUE _self) {
  MessageHeader* self;
222 223 224
  VALUE method_str;
  char* name;
  const upb_fielddef* f;
Chris Fallin's avatar
Chris Fallin committed
225 226 227 228 229 230 231
  TypedData_Get_Struct(_self, MessageHeader, &Message_type, self);

  if (!SYMBOL_P(key)) {
    rb_raise(rb_eArgError,
             "Expected symbols as hash keys in initialization map.");
  }

232 233 234
  method_str = rb_id2str(SYM2ID(key));
  name = RSTRING_PTR(method_str);
  f = upb_msgdef_ntofz(self->descriptor->msgdef, name);
Chris Fallin's avatar
Chris Fallin committed
235 236
  if (f == NULL) {
    rb_raise(rb_eArgError,
237
             "Unknown field name '%s' in initialization map entry.", name);
Chris Fallin's avatar
Chris Fallin committed
238 239
  }

240
  if (is_map_field(f)) {
241 242
    VALUE map;

243 244
    if (TYPE(val) != T_HASH) {
      rb_raise(rb_eArgError,
245
               "Expected Hash object as initializer value for map field '%s'.", name);
246
    }
247
    map = layout_get(self->descriptor->layout, Message_data(self), f);
248 249
    Map_merge_into_self(map, val);
  } else if (upb_fielddef_label(f) == UPB_LABEL_REPEATED) {
250 251
    VALUE ary;

Chris Fallin's avatar
Chris Fallin committed
252 253
    if (TYPE(val) != T_ARRAY) {
      rb_raise(rb_eArgError,
254
               "Expected array as initializer value for repeated field '%s'.", name);
Chris Fallin's avatar
Chris Fallin committed
255
    }
256
    ary = layout_get(self->descriptor->layout, Message_data(self), f);
Chris Fallin's avatar
Chris Fallin committed
257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
    for (int i = 0; i < RARRAY_LEN(val); i++) {
      RepeatedField_push(ary, rb_ary_entry(val, i));
    }
  } else {
    layout_set(self->descriptor->layout, Message_data(self), f, val);
  }
  return 0;
}

/*
 * call-seq:
 *     Message.new(kwargs) => new_message
 *
 * Creates a new instance of the given message class. Keyword arguments may be
 * provided with keywords corresponding to field names.
 *
 * Note that no literal Message class exists. Only concrete classes per message
 * type exist, as provided by the #msgclass method on Descriptors after they
 * have been added to a pool. The method definitions described here on the
 * Message class are provided on each concrete message class.
 */
VALUE Message_initialize(int argc, VALUE* argv, VALUE _self) {
279 280
  VALUE hash_args;

Chris Fallin's avatar
Chris Fallin committed
281 282 283 284 285 286
  if (argc == 0) {
    return Qnil;
  }
  if (argc != 1) {
    rb_raise(rb_eArgError, "Expected 0 or 1 arguments.");
  }
287
  hash_args = argv[0];
Chris Fallin's avatar
Chris Fallin committed
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303
  if (TYPE(hash_args) != T_HASH) {
    rb_raise(rb_eArgError, "Expected hash arguments.");
  }

  rb_hash_foreach(hash_args, Message_initialize_kwarg, _self);
  return Qnil;
}

/*
 * call-seq:
 *     Message.dup => new_message
 *
 * Performs a shallow copy of this message and returns the new copy.
 */
VALUE Message_dup(VALUE _self) {
  MessageHeader* self;
304 305
  VALUE new_msg;
  MessageHeader* new_msg_self;
Chris Fallin's avatar
Chris Fallin committed
306 307
  TypedData_Get_Struct(_self, MessageHeader, &Message_type, self);

308
  new_msg = rb_class_new_instance(0, NULL, CLASS_OF(_self));
Chris Fallin's avatar
Chris Fallin committed
309 310 311 312 313 314 315 316 317 318 319 320
  TypedData_Get_Struct(new_msg, MessageHeader, &Message_type, new_msg_self);

  layout_dup(self->descriptor->layout,
             Message_data(new_msg_self),
             Message_data(self));

  return new_msg;
}

// Internal only; used by Google::Protobuf.deep_copy.
VALUE Message_deep_copy(VALUE _self) {
  MessageHeader* self;
321 322
  MessageHeader* new_msg_self;
  VALUE new_msg;
Chris Fallin's avatar
Chris Fallin committed
323 324
  TypedData_Get_Struct(_self, MessageHeader, &Message_type, self);

325
  new_msg = rb_class_new_instance(0, NULL, CLASS_OF(_self));
Chris Fallin's avatar
Chris Fallin committed
326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344
  TypedData_Get_Struct(new_msg, MessageHeader, &Message_type, new_msg_self);

  layout_deep_copy(self->descriptor->layout,
                   Message_data(new_msg_self),
                   Message_data(self));

  return new_msg;
}

/*
 * call-seq:
 *     Message.==(other) => boolean
 *
 * Performs a deep comparison of this message with another. Messages are equal
 * if they have the same type and if each field is equal according to the :==
 * method's semantics (a more efficient comparison may actually be done if the
 * field is of a primitive type).
 */
VALUE Message_eq(VALUE _self, VALUE _other) {
345 346
  MessageHeader* self;
  MessageHeader* other;
347 348 349
  if (TYPE(_self) != TYPE(_other)) {
    return Qfalse;
  }
350
  TypedData_Get_Struct(_self, MessageHeader, &Message_type, self);
Chris Fallin's avatar
Chris Fallin committed
351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384
  TypedData_Get_Struct(_other, MessageHeader, &Message_type, other);

  if (self->descriptor != other->descriptor) {
    return Qfalse;
  }

  return layout_eq(self->descriptor->layout,
                   Message_data(self),
                   Message_data(other));
}

/*
 * call-seq:
 *     Message.hash => hash_value
 *
 * Returns a hash value that represents this message's field values.
 */
VALUE Message_hash(VALUE _self) {
  MessageHeader* self;
  TypedData_Get_Struct(_self, MessageHeader, &Message_type, self);

  return layout_hash(self->descriptor->layout, Message_data(self));
}

/*
 * call-seq:
 *     Message.inspect => string
 *
 * Returns a human-readable string representing this message. It will be
 * formatted as "<MessageType: field1: value1, field2: value2, ...>". Each
 * field's value is represented according to its own #inspect method.
 */
VALUE Message_inspect(VALUE _self) {
  MessageHeader* self;
385
  VALUE str;
Chris Fallin's avatar
Chris Fallin committed
386 387
  TypedData_Get_Struct(_self, MessageHeader, &Message_type, self);

388
  str = rb_str_new2("<");
Chris Fallin's avatar
Chris Fallin committed
389 390 391 392 393 394 395 396
  str = rb_str_append(str, rb_str_new2(rb_class2name(CLASS_OF(_self))));
  str = rb_str_cat2(str, ": ");
  str = rb_str_append(str, layout_inspect(
      self->descriptor->layout, Message_data(self)));
  str = rb_str_cat2(str, ">");
  return str;
}

397 398 399 400 401 402
/*
 * call-seq:
 *     Message.to_h => {}
 *
 * Returns the message as a Ruby Hash object, with keys as symbols.
 */
403 404
VALUE Message_to_h(VALUE _self) {
  MessageHeader* self;
405 406
  VALUE hash;
  upb_msg_field_iter it;
407 408
  TypedData_Get_Struct(_self, MessageHeader, &Message_type, self);

409
  hash = rb_hash_new();
410 411 412 413 414

  for (upb_msg_field_begin(&it, self->descriptor->msgdef);
       !upb_msg_field_done(&it);
       upb_msg_field_next(&it)) {
    const upb_fielddef* field = upb_msg_iter_field(&it);
415 416
    VALUE msg_value = layout_get(self->descriptor->layout, Message_data(self),
                                 field);
417
    VALUE msg_key   = ID2SYM(rb_intern(upb_fielddef_name(field)));
418 419 420
    if (upb_fielddef_ismap(field)) {
      msg_value = Map_to_h(msg_value);
    } else if (upb_fielddef_label(field) == UPB_LABEL_REPEATED) {
421
      msg_value = RepeatedField_to_ary(msg_value);
422 423 424
    } else if (msg_value != Qnil &&
               upb_fielddef_type(field) == UPB_TYPE_MESSAGE) {
      msg_value = Message_to_h(msg_value);
425 426 427 428 429 430 431 432
    }
    rb_hash_aset(hash, msg_key, msg_value);
  }
  return hash;
}



Chris Fallin's avatar
Chris Fallin committed
433 434 435 436 437 438 439 440 441
/*
 * call-seq:
 *     Message.[](index) => value
 *
 * Accesses a field's value by field name. The provided field name should be a
 * string.
 */
VALUE Message_index(VALUE _self, VALUE field_name) {
  MessageHeader* self;
442
  const upb_fielddef* field;
Chris Fallin's avatar
Chris Fallin committed
443 444
  TypedData_Get_Struct(_self, MessageHeader, &Message_type, self);
  Check_Type(field_name, T_STRING);
445
  field = upb_msgdef_ntofz(self->descriptor->msgdef, RSTRING_PTR(field_name));
Chris Fallin's avatar
Chris Fallin committed
446 447 448 449 450 451 452 453 454 455 456 457 458 459 460
  if (field == NULL) {
    return Qnil;
  }
  return layout_get(self->descriptor->layout, Message_data(self), field);
}

/*
 * call-seq:
 *     Message.[]=(index, value)
 *
 * Sets a field's value by field name. The provided field name should be a
 * string.
 */
VALUE Message_index_set(VALUE _self, VALUE field_name, VALUE value) {
  MessageHeader* self;
461
  const upb_fielddef* field;
Chris Fallin's avatar
Chris Fallin committed
462 463
  TypedData_Get_Struct(_self, MessageHeader, &Message_type, self);
  Check_Type(field_name, T_STRING);
464
  field = upb_msgdef_ntofz(self->descriptor->msgdef, RSTRING_PTR(field_name));
Chris Fallin's avatar
Chris Fallin committed
465 466 467 468 469 470 471 472 473 474 475 476 477 478 479
  if (field == NULL) {
    rb_raise(rb_eArgError, "Unknown field: %s", RSTRING_PTR(field_name));
  }
  layout_set(self->descriptor->layout, Message_data(self), field, value);
  return Qnil;
}

/*
 * call-seq:
 *     Message.descriptor => descriptor
 *
 * Class method that returns the Descriptor instance corresponding to this
 * message class's type.
 */
VALUE Message_descriptor(VALUE klass) {
480
  return rb_ivar_get(klass, descriptor_instancevar_interned);
Chris Fallin's avatar
Chris Fallin committed
481 482 483
}

VALUE build_class_from_descriptor(Descriptor* desc) {
484 485 486
  const char *name;
  VALUE klass;

Chris Fallin's avatar
Chris Fallin committed
487 488 489 490 491 492 493
  if (desc->layout == NULL) {
    desc->layout = create_layout(desc->msgdef);
  }
  if (desc->fill_method == NULL) {
    desc->fill_method = new_fillmsg_decodermethod(desc, &desc->fill_method);
  }

494
  name = upb_msgdef_fullname(desc->msgdef);
Chris Fallin's avatar
Chris Fallin committed
495 496 497 498
  if (name == NULL) {
    rb_raise(rb_eRuntimeError, "Descriptor does not have assigned name.");
  }

499
  klass = rb_define_class_id(
Chris Fallin's avatar
Chris Fallin committed
500 501 502 503
      // Docs say this parameter is ignored. User will assign return value to
      // their own toplevel constant class name.
      rb_intern("Message"),
      rb_cObject);
504 505
  rb_ivar_set(klass, descriptor_instancevar_interned,
              get_def_obj(desc->msgdef));
Chris Fallin's avatar
Chris Fallin committed
506
  rb_define_alloc_func(klass, Message_alloc);
507 508
  rb_require("google/protobuf/message_exts");
  rb_include_module(klass, rb_eval_string("Google::Protobuf::MessageExts"));
509 510
  rb_extend_object(
      klass, rb_eval_string("Google::Protobuf::MessageExts::ClassMethods"));
511

Chris Fallin's avatar
Chris Fallin committed
512 513
  rb_define_method(klass, "method_missing",
                   Message_method_missing, -1);
514 515
  rb_define_method(klass, "respond_to_missing?",
                   Message_respond_to_missing, -1);
Chris Fallin's avatar
Chris Fallin committed
516 517 518 519 520 521
  rb_define_method(klass, "initialize", Message_initialize, -1);
  rb_define_method(klass, "dup", Message_dup, 0);
  // Also define #clone so that we don't inherit Object#clone.
  rb_define_method(klass, "clone", Message_dup, 0);
  rb_define_method(klass, "==", Message_eq, 1);
  rb_define_method(klass, "hash", Message_hash, 0);
522 523
  rb_define_method(klass, "to_h", Message_to_h, 0);
  rb_define_method(klass, "to_hash", Message_to_h, 0);
Chris Fallin's avatar
Chris Fallin committed
524 525 526 527 528 529
  rb_define_method(klass, "inspect", Message_inspect, 0);
  rb_define_method(klass, "[]", Message_index, 1);
  rb_define_method(klass, "[]=", Message_index_set, 2);
  rb_define_singleton_method(klass, "decode", Message_decode, 1);
  rb_define_singleton_method(klass, "encode", Message_encode, 1);
  rb_define_singleton_method(klass, "decode_json", Message_decode_json, 1);
530
  rb_define_singleton_method(klass, "encode_json", Message_encode_json, -1);
Chris Fallin's avatar
Chris Fallin committed
531
  rb_define_singleton_method(klass, "descriptor", Message_descriptor, 0);
532

Chris Fallin's avatar
Chris Fallin committed
533 534 535 536 537 538 539 540 541 542 543 544
  return klass;
}

/*
 * call-seq:
 *     Enum.lookup(number) => name
 *
 * This module method, provided on each generated enum module, looks up an enum
 * value by number and returns its name as a Ruby symbol, or nil if not found.
 */
VALUE enum_lookup(VALUE self, VALUE number) {
  int32_t num = NUM2INT(number);
545
  VALUE desc = rb_ivar_get(self, descriptor_instancevar_interned);
Chris Fallin's avatar
Chris Fallin committed
546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564
  EnumDescriptor* enumdesc = ruby_to_EnumDescriptor(desc);

  const char* name = upb_enumdef_iton(enumdesc->enumdef, num);
  if (name == NULL) {
    return Qnil;
  } else {
    return ID2SYM(rb_intern(name));
  }
}

/*
 * call-seq:
 *     Enum.resolve(name) => number
 *
 * This module method, provided on each generated enum module, looks up an enum
 * value by name (as a Ruby symbol) and returns its name, or nil if not found.
 */
VALUE enum_resolve(VALUE self, VALUE sym) {
  const char* name = rb_id2name(SYM2ID(sym));
565
  VALUE desc = rb_ivar_get(self, descriptor_instancevar_interned);
Chris Fallin's avatar
Chris Fallin committed
566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584
  EnumDescriptor* enumdesc = ruby_to_EnumDescriptor(desc);

  int32_t num = 0;
  bool found = upb_enumdef_ntoiz(enumdesc->enumdef, name, &num);
  if (!found) {
    return Qnil;
  } else {
    return INT2NUM(num);
  }
}

/*
 * call-seq:
 *     Enum.descriptor
 *
 * This module method, provided on each generated enum module, returns the
 * EnumDescriptor corresponding to this enum type.
 */
VALUE enum_descriptor(VALUE self) {
585
  return rb_ivar_get(self, descriptor_instancevar_interned);
Chris Fallin's avatar
Chris Fallin committed
586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609
}

VALUE build_module_from_enumdesc(EnumDescriptor* enumdesc) {
  VALUE mod = rb_define_module_id(
      rb_intern(upb_enumdef_fullname(enumdesc->enumdef)));

  upb_enum_iter it;
  for (upb_enum_begin(&it, enumdesc->enumdef);
       !upb_enum_done(&it);
       upb_enum_next(&it)) {
    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,
               "Enum value '%s' does not start with an uppercase letter "
               "as is required for Ruby constants.",
               name);
    }
    rb_define_const(mod, name, INT2NUM(value));
  }

  rb_define_singleton_method(mod, "lookup", enum_lookup, 1);
  rb_define_singleton_method(mod, "resolve", enum_resolve, 1);
  rb_define_singleton_method(mod, "descriptor", enum_descriptor, 0);
610 611
  rb_ivar_set(mod, descriptor_instancevar_interned,
              get_def_obj(enumdesc->enumdef));
Chris Fallin's avatar
Chris Fallin committed
612 613 614 615 616 617 618 619

  return mod;
}

/*
 * call-seq:
 *     Google::Protobuf.deep_copy(obj) => copy_of_obj
 *
620 621
 * Performs a deep copy of a RepeatedField instance, a Map instance, or a
 * message object, recursively copying its members.
Chris Fallin's avatar
Chris Fallin committed
622 623 624 625 626
 */
VALUE Google_Protobuf_deep_copy(VALUE self, VALUE obj) {
  VALUE klass = CLASS_OF(obj);
  if (klass == cRepeatedField) {
    return RepeatedField_deep_copy(obj);
627 628
  } else if (klass == cMap) {
    return Map_deep_copy(obj);
Chris Fallin's avatar
Chris Fallin committed
629 630 631 632
  } else {
    return Message_deep_copy(obj);
  }
}