aboutsummaryrefslogtreecommitdiffhomepage
path: root/ruby
diff options
context:
space:
mode:
authorGravatar Josh Haberman <jhaberman@gmail.com>2016-07-27 17:31:16 -0700
committerGravatar Josh Haberman <jhaberman@gmail.com>2016-07-28 17:02:33 -0700
commite3094a8d803c0b9df33a8a506cce28a89c5fd6e8 (patch)
tree67cd5cecd7a495f18c441163355e9d04c2bcfa17 /ruby
parentba52f2b6780fa5e6bee86cf7e8ee6f6ba617862c (diff)
Ruby: added API support for well-known types.
Diffstat (limited to 'ruby')
-rw-r--r--ruby/lib/google/protobuf/well_known_types.rb206
-rw-r--r--ruby/tests/well_known_types_test.rb98
2 files changed, 304 insertions, 0 deletions
diff --git a/ruby/lib/google/protobuf/well_known_types.rb b/ruby/lib/google/protobuf/well_known_types.rb
new file mode 100644
index 00000000..d0d15045
--- /dev/null
+++ b/ruby/lib/google/protobuf/well_known_types.rb
@@ -0,0 +1,206 @@
+#!/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
+
+ 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 "Value not set"
+ end
+ end
+
+ def from_ruby(value)
+ if value.nil?
+ self.null_value = 0
+ elsif value.is_a?(Numeric)
+ self.number_value = value
+ elsif value.is_a?(String)
+ self.string_value = value
+ elsif value.is_a?(TrueClass)
+ self.bool_value = true
+ elsif value.is_a?(FalseClass)
+ self.bool_value = false
+ elsif value.is_a?(Struct)
+ self.struct_value = value
+ elsif value.is_a?(Hash)
+ self.struct_value = Struct.from_hash(value)
+ elsif value.is_a?(ListValue)
+ self.list_value = value
+ elsif value.is_a?(Array)
+ self.list_value = ListValue.from_a(value)
+ else
+ raise "Unexpected type"
+ end
+ end
+ end
+
+ Struct.class_eval do
+ def [](key)
+ self.fields[key].to_ruby
+ end
+
+ def []=(key, value)
+ 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
diff --git a/ruby/tests/well_known_types_test.rb b/ruby/tests/well_known_types_test.rb
new file mode 100644
index 00000000..cf9a3aa0
--- /dev/null
+++ b/ruby/tests/well_known_types_test.rb
@@ -0,0 +1,98 @@
+#!/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)
+ end
+
+ def test_any
+ any = Google::Protobuf::Any.new
+ ts = Google::Protobuf::Timestamp.new(seconds: 12345, nanos: 6789)
+ any.pack(ts)
+
+ assert_true any.is(Google::Protobuf::Timestamp)
+ assert_equal ts, any.unpack(Google::Protobuf::Timestamp)
+ end
+end