Merge pull request #1878 from haberman/rubywkt
Ruby: added API support for well-known types.
This commit is contained in:
commit
8d8115bf52
@ -682,6 +682,7 @@ ruby_EXTRA_DIST= \
|
||||
ruby/google-protobuf.gemspec \
|
||||
ruby/lib/google/protobuf/message_exts.rb \
|
||||
ruby/lib/google/protobuf/repeated_field.rb \
|
||||
ruby/lib/google/protobuf/well_known_types.rb \
|
||||
ruby/lib/google/protobuf.rb \
|
||||
ruby/pom.xml \
|
||||
ruby/src/main/java/com/google/protobuf/jruby/RubyBuilder.java \
|
||||
@ -708,6 +709,7 @@ ruby_EXTRA_DIST= \
|
||||
ruby/tests/generated_code.proto \
|
||||
ruby/tests/test_import.proto \
|
||||
ruby/tests/generated_code_test.rb \
|
||||
ruby/tests/well_known_types_test.rb \
|
||||
ruby/travis-test.sh
|
||||
|
||||
js_EXTRA_DIST= \
|
||||
|
212
ruby/lib/google/protobuf/well_known_types.rb
Normal file
212
ruby/lib/google/protobuf/well_known_types.rb
Normal file
@ -0,0 +1,212 @@
|
||||
#!/usr/bin/ruby
|
||||
# Protocol Buffers - Google's data interchange format
|
||||
# Copyright 2008 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.
|
||||
|
||||
require 'google/protobuf/any_pb'
|
||||
require 'google/protobuf/duration_pb'
|
||||
require 'google/protobuf/field_mask_pb'
|
||||
require 'google/protobuf/struct_pb'
|
||||
require 'google/protobuf/timestamp_pb'
|
||||
|
||||
module Google
|
||||
module Protobuf
|
||||
|
||||
Any.class_eval do
|
||||
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}"
|
||||
else
|
||||
self.type_url = "#{type_url_prefix}#{msg.class.descriptor.name}"
|
||||
end
|
||||
self.value = msg.to_proto
|
||||
end
|
||||
|
||||
def unpack(klass)
|
||||
if self.is(klass) then
|
||||
klass.decode(self.value)
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def type_name
|
||||
return self.type_url.split("/")[-1]
|
||||
end
|
||||
|
||||
def is(klass)
|
||||
return self.type_name == klass.descriptor.name
|
||||
end
|
||||
end
|
||||
|
||||
Timestamp.class_eval do
|
||||
def to_time
|
||||
Time.at(self.to_f)
|
||||
end
|
||||
|
||||
def from_time(time)
|
||||
self.seconds = time.to_i
|
||||
self.nanos = time.nsec
|
||||
end
|
||||
|
||||
def to_i
|
||||
self.seconds
|
||||
end
|
||||
|
||||
def to_f
|
||||
self.seconds + (self.nanos.to_f / 1_000_000_000)
|
||||
end
|
||||
end
|
||||
|
||||
Duration.class_eval do
|
||||
def to_f
|
||||
self.seconds + (self.nanos.to_f / 1_000_000_000)
|
||||
end
|
||||
end
|
||||
|
||||
class UnexpectedStructType < Google::Protobuf::Error; end
|
||||
|
||||
Value.class_eval do
|
||||
def to_ruby(recursive = false)
|
||||
case self.kind
|
||||
when :struct_value
|
||||
if recursive
|
||||
self.struct_value.to_h
|
||||
else
|
||||
self.struct_value
|
||||
end
|
||||
when :list_value
|
||||
if recursive
|
||||
self.list_value.to_a
|
||||
else
|
||||
self.list_value
|
||||
end
|
||||
when :null_value
|
||||
nil
|
||||
when :number_value
|
||||
self.number_value
|
||||
when :string_value
|
||||
self.string_value
|
||||
when :bool_value
|
||||
self.bool_value
|
||||
else
|
||||
raise UnexpectedStructType
|
||||
end
|
||||
end
|
||||
|
||||
def from_ruby(value)
|
||||
case value
|
||||
when NilClass
|
||||
self.null_value = 0
|
||||
when Numeric
|
||||
self.number_value = value
|
||||
when String
|
||||
self.string_value = value
|
||||
when TrueClass
|
||||
self.bool_value = true
|
||||
when FalseClass
|
||||
self.bool_value = false
|
||||
when Struct
|
||||
self.struct_value = value
|
||||
when Hash
|
||||
self.struct_value = Struct.from_hash(value)
|
||||
when ListValue
|
||||
self.list_value = value
|
||||
when Array
|
||||
self.list_value = ListValue.from_a(value)
|
||||
else
|
||||
raise UnexpectedStructType
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Struct.class_eval do
|
||||
def [](key)
|
||||
self.fields[key].to_ruby
|
||||
end
|
||||
|
||||
def []=(key, value)
|
||||
unless key.is_a?(String)
|
||||
raise UnexpectedStructType, "Struct keys must be strings."
|
||||
end
|
||||
self.fields[key] ||= Google::Protobuf::Value.new
|
||||
self.fields[key].from_ruby(value)
|
||||
end
|
||||
|
||||
def to_h
|
||||
ret = {}
|
||||
self.fields.each { |key, val| ret[key] = val.to_ruby(true) }
|
||||
ret
|
||||
end
|
||||
|
||||
def self.from_hash(hash)
|
||||
ret = Struct.new
|
||||
hash.each { |key, val| ret[key] = val }
|
||||
ret
|
||||
end
|
||||
end
|
||||
|
||||
ListValue.class_eval do
|
||||
include Enumerable
|
||||
|
||||
def length
|
||||
self.values.length
|
||||
end
|
||||
|
||||
def [](index)
|
||||
self.values[index].to_ruby
|
||||
end
|
||||
|
||||
def []=(index, value)
|
||||
self.values[index].from_ruby(value)
|
||||
end
|
||||
|
||||
def <<(value)
|
||||
wrapper = Google::Protobuf::Value.new
|
||||
wrapper.from_ruby(value)
|
||||
self.values << wrapper
|
||||
end
|
||||
|
||||
def each
|
||||
self.values.each { |x| yield(x.to_ruby) }
|
||||
end
|
||||
|
||||
def to_a
|
||||
self.values.map { |x| x.to_ruby(true) }
|
||||
end
|
||||
|
||||
def self.from_a(arr)
|
||||
ret = ListValue.new
|
||||
arr.each { |val| ret << val }
|
||||
ret
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
@ -468,9 +468,9 @@ module BasicTest
|
||||
assert m.length == 2
|
||||
|
||||
m2 = m.dup
|
||||
assert m == m2
|
||||
assert_equal m, m2
|
||||
assert m.hash != 0
|
||||
assert m.hash == m2.hash
|
||||
assert_equal m.hash, m2.hash
|
||||
|
||||
collected = {}
|
||||
m.each { |k,v| collected[v] = k }
|
||||
|
122
ruby/tests/well_known_types_test.rb
Normal file
122
ruby/tests/well_known_types_test.rb
Normal file
@ -0,0 +1,122 @@
|
||||
#!/usr/bin/ruby
|
||||
|
||||
require 'test/unit'
|
||||
require 'google/protobuf/well_known_types'
|
||||
|
||||
class TestWellKnownTypes < Test::Unit::TestCase
|
||||
def test_timestamp
|
||||
ts = Google::Protobuf::Timestamp.new
|
||||
|
||||
assert_equal Time.at(0), ts.to_time
|
||||
|
||||
ts.seconds = 12345
|
||||
assert_equal Time.at(12345), ts.to_time
|
||||
assert_equal 12345, ts.to_i
|
||||
|
||||
ts.from_time(Time.at(123456, 654321))
|
||||
assert_equal 123456, ts.seconds
|
||||
assert_equal 654321000, ts.nanos
|
||||
assert_equal Time.at(123456.654321), ts.to_time
|
||||
end
|
||||
|
||||
def test_duration
|
||||
duration = Google::Protobuf::Duration.new(seconds: 123, nanos: 456)
|
||||
assert_equal 123.000000456, duration.to_f
|
||||
end
|
||||
|
||||
def test_struct
|
||||
struct = Google::Protobuf::Struct.new
|
||||
|
||||
substruct = {
|
||||
"subkey" => 999,
|
||||
"subkey2" => false
|
||||
}
|
||||
|
||||
sublist = ["abc", 123, {"deepkey" => "deepval"}]
|
||||
|
||||
struct["number"] = 12345
|
||||
struct["boolean-true"] = true
|
||||
struct["boolean-false"] = false
|
||||
struct["null"] = nil
|
||||
struct["string"] = "abcdef"
|
||||
struct["substruct"] = substruct
|
||||
struct["sublist"] = sublist
|
||||
|
||||
assert_equal 12345, struct["number"]
|
||||
assert_equal true, struct["boolean-true"]
|
||||
assert_equal false, struct["boolean-false"]
|
||||
assert_equal nil, struct["null"]
|
||||
assert_equal "abcdef", struct["string"]
|
||||
assert_equal(Google::Protobuf::Struct.from_hash(substruct),
|
||||
struct["substruct"])
|
||||
assert_equal(Google::Protobuf::ListValue.from_a(sublist),
|
||||
struct["sublist"])
|
||||
|
||||
should_equal = {
|
||||
"number" => 12345,
|
||||
"boolean-true" => true,
|
||||
"boolean-false" => false,
|
||||
"null" => nil,
|
||||
"string" => "abcdef",
|
||||
"substruct" => {
|
||||
"subkey" => 999,
|
||||
"subkey2" => false
|
||||
},
|
||||
"sublist" => ["abc", 123, {"deepkey" => "deepval"}]
|
||||
}
|
||||
|
||||
list = struct["sublist"]
|
||||
list.is_a?(Google::Protobuf::ListValue)
|
||||
assert_equal "abc", list[0]
|
||||
assert_equal 123, list[1]
|
||||
assert_equal({"deepkey" => "deepval"}, list[2].to_h)
|
||||
|
||||
# to_h returns a fully-flattened Ruby structure (Hash and Array).
|
||||
assert_equal(should_equal, struct.to_h)
|
||||
|
||||
# Test that we can assign Struct and ListValue directly.
|
||||
struct["substruct"] = Google::Protobuf::Struct.from_hash(substruct)
|
||||
struct["sublist"] = Google::Protobuf::ListValue.from_a(sublist)
|
||||
|
||||
assert_equal(should_equal, struct.to_h)
|
||||
|
||||
struct["sublist"] << nil
|
||||
should_equal["sublist"] << nil
|
||||
|
||||
assert_equal(should_equal, struct.to_h)
|
||||
assert_equal(should_equal["sublist"].length, struct["sublist"].length)
|
||||
|
||||
assert_raise Google::Protobuf::UnexpectedStructType do
|
||||
struct[123] = 5
|
||||
end
|
||||
|
||||
assert_raise Google::Protobuf::UnexpectedStructType do
|
||||
struct[5] = Time.new
|
||||
end
|
||||
|
||||
assert_raise Google::Protobuf::UnexpectedStructType do
|
||||
struct[5] = [Time.new]
|
||||
end
|
||||
|
||||
assert_raise Google::Protobuf::UnexpectedStructType do
|
||||
struct[5] = {123 => 456}
|
||||
end
|
||||
|
||||
assert_raise Google::Protobuf::UnexpectedStructType do
|
||||
struct = Google::Protobuf::Struct.new
|
||||
struct.fields["foo"] = Google::Protobuf::Value.new
|
||||
# Tries to return a Ruby value for a Value class whose type
|
||||
# hasn't been filled in.
|
||||
struct["foo"]
|
||||
end
|
||||
end
|
||||
|
||||
def test_any
|
||||
any = Google::Protobuf::Any.new
|
||||
ts = Google::Protobuf::Timestamp.new(seconds: 12345, nanos: 6789)
|
||||
any.pack(ts)
|
||||
|
||||
assert any.is(Google::Protobuf::Timestamp)
|
||||
assert_equal ts, any.unpack(Google::Protobuf::Timestamp)
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue
Block a user