Add native type setters for Timestamp and Duration in Ruby (#5751)

* add implicit time conversion

* add duration

* add init test

* more tests

* add type check and alternative c type check

* add rational and bigdecimal

* use rb_obj_is_kind_of

* use native time check

* chain implicit conversions

* remove unused variable
pull/5739/head^2
Joe Bolinger 6 years ago committed by Paul Yang
parent e9faff8514
commit d2daa38986
  1. 36
      ruby/ext/google/protobuf_c/storage.c
  2. 7
      ruby/tests/basic_test.proto
  3. 7
      ruby/tests/basic_test_proto2.proto
  4. 50
      ruby/tests/common_tests.rb

@ -178,9 +178,39 @@ void native_slot_set_value_and_case(const char* name,
if (CLASS_OF(value) == CLASS_OF(Qnil)) {
value = Qnil;
} else if (CLASS_OF(value) != type_class) {
rb_raise(cTypeError,
"Invalid type %s to assign to submessage field '%s'.",
rb_class2name(CLASS_OF(value)), name);
// check for possible implicit conversions
VALUE converted_value = NULL;
char* field_type_name = rb_class2name(type_class);
if (strcmp(field_type_name, "Google::Protobuf::Timestamp") == 0 &&
rb_obj_is_kind_of(value, rb_cTime)) {
// Time -> Google::Protobuf::Timestamp
VALUE hash = rb_hash_new();
rb_hash_aset(hash, rb_str_new2("seconds"), rb_funcall(value, rb_intern("to_i"), 0));
rb_hash_aset(hash, rb_str_new2("nanos"), rb_funcall(value, rb_intern("nsec"), 0));
VALUE args[1] = { hash };
converted_value = rb_class_new_instance(1, args, type_class);
} else if (strcmp(field_type_name, "Google::Protobuf::Duration") == 0 &&
rb_obj_is_kind_of(value, rb_cNumeric)) {
// Numeric -> Google::Protobuf::Duration
VALUE hash = rb_hash_new();
rb_hash_aset(hash, rb_str_new2("seconds"), rb_funcall(value, rb_intern("to_i"), 0));
VALUE n_value = rb_funcall(value, rb_intern("remainder"), 1, INT2NUM(1));
n_value = rb_funcall(n_value, rb_intern("*"), 1, INT2NUM(1000000000));
n_value = rb_funcall(n_value, rb_intern("round"), 0);
rb_hash_aset(hash, rb_str_new2("nanos"), n_value);
VALUE args[1] = { hash };
converted_value = rb_class_new_instance(1, args, type_class);
}
// raise if no suitable conversaion could be found
if (converted_value == NULL) {
rb_raise(cTypeError,
"Invalid type %s to assign to submessage field '%s'.",
rb_class2name(CLASS_OF(value)), name);
} else {
value = converted_value;
}
}
DEREF(memory, VALUE) = value;
break;

@ -2,6 +2,8 @@ syntax = "proto3";
package basic_test;
import "google/protobuf/timestamp.proto";
import "google/protobuf/duration.proto";
import "google/protobuf/struct.proto";
message Foo {
@ -110,6 +112,11 @@ message Outer {
message Inner {
}
message TimeMessage {
google.protobuf.Timestamp timestamp = 1;
google.protobuf.Duration duration = 2;
}
message Enumer {
TestEnum optional_enum = 1;
repeated TestEnum repeated_enum = 2;

@ -2,6 +2,8 @@ syntax = "proto2";
package basic_test_proto2;
import "google/protobuf/timestamp.proto";
import "google/protobuf/duration.proto";
import "google/protobuf/struct.proto";
message Foo {
@ -118,6 +120,11 @@ message OneofMessage {
}
}
message TimeMessage {
optional google.protobuf.Timestamp timestamp = 1;
optional google.protobuf.Duration duration = 2;
}
message Enumer {
optional TestEnum optional_enum = 11;
repeated TestEnum repeated_enum = 22;

@ -3,6 +3,9 @@
# Requires that the proto messages are exactly the same in proto2 and proto3 syntax
# and that the including class should define a 'proto_module' method which returns
# the enclosing module of the proto message classes.
require 'bigdecimal'
module CommonTests
# Ruby 2.5 changed to raise FrozenError instead of RuntimeError
FrozenErrorType = Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.5') ? RuntimeError : FrozenError
@ -1264,6 +1267,53 @@ module CommonTests
assert proto_module::TestMessage.new != nil
end
def test_converts_time
m = proto_module::TimeMessage.new
m.timestamp = Google::Protobuf::Timestamp.new(seconds: 5, nanos: 6)
assert_kind_of Google::Protobuf::Timestamp, m.timestamp
assert_equal 5, m.timestamp.seconds
assert_equal 6, m.timestamp.nanos
m.timestamp = Time.at(9466, 123456.789)
assert_equal Google::Protobuf::Timestamp.new(seconds: 9466, nanos: 123456789), m.timestamp
m = proto_module::TimeMessage.new(timestamp: Time.at(1))
assert_equal Google::Protobuf::Timestamp.new(seconds: 1, nanos: 0), m.timestamp
assert_raise(Google::Protobuf::TypeError) { m.timestamp = 2 }
assert_raise(Google::Protobuf::TypeError) { m.timestamp = 2.4 }
assert_raise(Google::Protobuf::TypeError) { m.timestamp = '4' }
assert_raise(Google::Protobuf::TypeError) { m.timestamp = proto_module::TimeMessage.new }
end
def test_converts_duration
m = proto_module::TimeMessage.new
m.duration = Google::Protobuf::Duration.new(seconds: 2, nanos: 22)
assert_kind_of Google::Protobuf::Duration, m.duration
assert_equal 2, m.duration.seconds
assert_equal 22, m.duration.nanos
m.duration = 10.5
assert_equal Google::Protobuf::Duration.new(seconds: 10, nanos: 500_000_000), m.duration
m.duration = 200
assert_equal Google::Protobuf::Duration.new(seconds: 200, nanos: 0), m.duration
m.duration = Rational(3, 2)
assert_equal Google::Protobuf::Duration.new(seconds: 1, nanos: 500_000_000), m.duration
m.duration = BigDecimal.new("5")
assert_equal Google::Protobuf::Duration.new(seconds: 5, nanos: 0), m.duration
m = proto_module::TimeMessage.new(duration: 1.1)
assert_equal Google::Protobuf::Duration.new(seconds: 1, nanos: 100_000_000), m.duration
assert_raise(Google::Protobuf::TypeError) { m.duration = '2' }
assert_raise(Google::Protobuf::TypeError) { m.duration = proto_module::TimeMessage.new }
end
def test_freeze
m = proto_module::TestMessage.new
m.optional_int32 = 10

Loading…
Cancel
Save