protip 0.20.7 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/protip.rb +10 -0
- data/lib/protip/{wrapper.rb → decorator.rb} +98 -82
- data/lib/protip/extensions.rb +0 -1
- data/lib/protip/resource.rb +56 -32
- data/lib/protip/resource/extra_methods.rb +5 -3
- data/lib/protip/tasks/compile.rake +2 -2
- data/lib/protip/transformer.rb +14 -0
- data/lib/protip/transformers/abstract_transformer.rb +11 -0
- data/lib/protip/transformers/active_support/time_with_zone_transformer.rb +35 -0
- data/lib/protip/transformers/decorating_transformer.rb +33 -0
- data/lib/protip/transformers/default_transformer.rb +22 -0
- data/lib/protip/transformers/delegating_transformer.rb +44 -0
- data/lib/protip/transformers/deprecated_transformer.rb +90 -0
- data/lib/protip/transformers/enum_transformer.rb +93 -0
- data/lib/protip/transformers/primitives_transformer.rb +78 -0
- data/lib/protip/transformers/timestamp_transformer.rb +31 -0
- data/test/functional/protip/decorator_test.rb +204 -0
- data/test/unit/protip/decorator_test.rb +665 -0
- data/test/unit/protip/resource_test.rb +76 -92
- data/test/unit/protip/transformers/decorating_transformer_test.rb +92 -0
- data/test/unit/protip/transformers/delegating_transformer_test.rb +83 -0
- data/test/unit/protip/transformers/deprecated_transformer_test.rb +139 -0
- data/test/unit/protip/transformers/enum_transformer_test.rb +157 -0
- data/test/unit/protip/transformers/primitives_transformer_test.rb +139 -0
- data/test/unit/protip/transformers/timestamp_transformer_test.rb +46 -0
- metadata +23 -12
- data/definitions/google/protobuf/wrappers.proto +0 -99
- data/lib/google/protobuf/descriptor.rb +0 -1
- data/lib/google/protobuf/wrappers.rb +0 -48
- data/lib/protip/converter.rb +0 -22
- data/lib/protip/standard_converter.rb +0 -185
- data/test/unit/protip/standard_converter_test.rb +0 -502
- data/test/unit/protip/wrapper_test.rb +0 -646
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'protip/decorator'
|
2
|
+
|
1
3
|
module Protip
|
2
4
|
module Resource
|
3
5
|
# Internal helpers for non-resourceful member/collection methods. Never use these directly;
|
@@ -9,15 +11,15 @@ module Protip
|
|
9
11
|
method: method,
|
10
12
|
message: message,
|
11
13
|
response_type: response_type
|
12
|
-
nil == response ? nil : ::Protip::
|
14
|
+
nil == response ? nil : ::Protip::Decorator.new(response, resource.class.transformer)
|
13
15
|
end
|
14
16
|
def self.collection(resource_class, action, method, message, response_type)
|
15
17
|
response = resource_class.client.request path: "#{resource_class.base_path}/#{action}",
|
16
18
|
method: method,
|
17
19
|
message: message,
|
18
20
|
response_type: response_type
|
19
|
-
nil == response ? nil : ::Protip::
|
21
|
+
nil == response ? nil : ::Protip::Decorator.new(response, resource_class.transformer)
|
20
22
|
end
|
21
23
|
end
|
22
24
|
end
|
23
|
-
end
|
25
|
+
end
|
@@ -10,10 +10,10 @@ namespace :protip do
|
|
10
10
|
filename = args[:filename] || raise(ArgumentError.new 'filename argument is required')
|
11
11
|
|
12
12
|
command = "protoc #{proto_path.map{|p| "--proto_path=#{Shellwords.escape p}"}.join ' '} --ruby_out=#{Shellwords.escape ruby_path} #{Shellwords.escape filename}"
|
13
|
-
puts command
|
13
|
+
puts command
|
14
14
|
system command
|
15
15
|
|
16
|
-
## hack around missing options in Ruby, remove when https://github.com/google/protobuf/issues/1198 is resolved
|
16
|
+
## ridiculous hack around missing options in Ruby, remove when https://github.com/google/protobuf/issues/1198 is resolved
|
17
17
|
package_match = File.read(filename).match(/package "?([a-zA-Z0-9\.]+)"?;/)
|
18
18
|
package = (package_match ? package_match[1] : nil)
|
19
19
|
ruby_file = filename.gsub(/^#{proto_path.first}\/?/, "#{ruby_path}/").gsub(/proto$/, 'rb') # Relies on a relative filename and proto path, which protoc requires anyway at this point
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Protip
|
2
|
+
# Interface for an object that converts between messages and more complex Ruby types. Resources and wrapped
|
3
|
+
# messages store one of these to transparently allow getting/setting of message fields as if they were
|
4
|
+
# Ruby types.
|
5
|
+
module Transformer
|
6
|
+
def to_object(message, field)
|
7
|
+
raise NotImplementedError.new('Must convert a message into a Ruby object')
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_message(object, field)
|
11
|
+
raise NotImplementedError.new('Must convert a Ruby object into a message of the given type')
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'protip/transformer'
|
2
|
+
|
3
|
+
module Protip
|
4
|
+
module Transformers
|
5
|
+
# Instantiable version of the +::Protip::Transformer+ concern,
|
6
|
+
# for when we need a placeholder transformer object.
|
7
|
+
class AbstractTransformer
|
8
|
+
include ::Protip::Transformer
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'protip/transformer'
|
2
|
+
require 'protip/messages/active_support/time_with_zone'
|
3
|
+
|
4
|
+
module Protip
|
5
|
+
module Transformers
|
6
|
+
module ActiveSupport
|
7
|
+
module TimeWithZoneTransformer
|
8
|
+
include Protip::Transformer
|
9
|
+
|
10
|
+
def transformable?(message_class)
|
11
|
+
message_class == ::Protip::Messages::ActiveSupport::TimeWithZone
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_object(message)
|
15
|
+
ActiveSupport::TimeWithZone.new(
|
16
|
+
Time.at(message.utc_timestamp).utc,
|
17
|
+
ActiveSupport::TimeZone.new(message.time_zone_name)
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_message(value, message_class)
|
22
|
+
if !value.is_a?(::ActiveSupport::TimeWithZone) && (value.is_a?(Time) || value.is_a?(DateTime))
|
23
|
+
value = ::ActiveSupport::TimeWithZone.new(value.to_time.utc, ::ActiveSupport::TimeZone.new('UTC'))
|
24
|
+
end
|
25
|
+
raise ArgumentError unless value.is_a?(::ActiveSupport::TimeWithZone)
|
26
|
+
|
27
|
+
message_class.new(
|
28
|
+
utc_timestamp: value.to_i,
|
29
|
+
time_zone_name: value.time_zone.name,
|
30
|
+
)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'protip/decorator'
|
2
|
+
require 'protip/transformer'
|
3
|
+
|
4
|
+
module Protip
|
5
|
+
module Transformers
|
6
|
+
# A transformer which decorates all messages, passing in another
|
7
|
+
# transformer (generally a "parent" delegating transformer with
|
8
|
+
# this as its fallback) to the generated decorators. Allows
|
9
|
+
# cascading message decoration.
|
10
|
+
class DecoratingTransformer
|
11
|
+
include Protip::Transformer
|
12
|
+
def initialize(transformer)
|
13
|
+
@transformer = transformer
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_object(message, field)
|
17
|
+
Protip::Decorator.new(message, @transformer)
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_message(object, field)
|
21
|
+
if object.is_a?(Protip::Decorator)
|
22
|
+
object.message
|
23
|
+
elsif object.is_a?(Hash)
|
24
|
+
decorator = Protip::Decorator.new(field.subtype.msgclass.new, @transformer)
|
25
|
+
decorator.assign_attributes(object)
|
26
|
+
decorator.message
|
27
|
+
else
|
28
|
+
raise ArgumentError
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'protip/transformers/decorating_transformer'
|
2
|
+
require 'protip/transformers/delegating_transformer'
|
3
|
+
require 'protip/transformers/deprecated_transformer'
|
4
|
+
require 'protip/transformers/enum_transformer'
|
5
|
+
require 'protip/transformers/primitives_transformer'
|
6
|
+
|
7
|
+
module Protip
|
8
|
+
module Transformers
|
9
|
+
# A transformer for our built-in types.
|
10
|
+
class DefaultTransformer < DelegatingTransformer
|
11
|
+
def initialize
|
12
|
+
# For message types that we don't recognize, just wrap them and pass
|
13
|
+
# ourself in as the transformer for their submessages.
|
14
|
+
super Protip::Transformers::DecoratingTransformer.new(self)
|
15
|
+
|
16
|
+
merge! Protip::Transformers::PrimitivesTransformer.new
|
17
|
+
merge! Protip::Transformers::EnumTransformer.new
|
18
|
+
merge! Protip::Transformers::DeprecatedTransformer.new
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'protip/transformer'
|
2
|
+
require 'protip/transformers/abstract_transformer'
|
3
|
+
|
4
|
+
module Protip
|
5
|
+
module Transformers
|
6
|
+
# A transformer which forwards to other transformers based on the message type
|
7
|
+
# being converted.
|
8
|
+
class DelegatingTransformer
|
9
|
+
include Protip::Transformer
|
10
|
+
extend Forwardable
|
11
|
+
|
12
|
+
# @param [Protip::Transformer] fallback_transformer The transformer to use
|
13
|
+
# for messages that don't have a registered transformer already.
|
14
|
+
# @param [Hash<String, Protip::Transformer>] transformers A message_name => transformer
|
15
|
+
# hash specifying which transformers to use for which message types.
|
16
|
+
def initialize(fallback_transformer = AbstractTransformer.new, transformers = {})
|
17
|
+
@fallback_transformer = fallback_transformer
|
18
|
+
@transformers = transformers.dup
|
19
|
+
end
|
20
|
+
|
21
|
+
def_delegators :@transformers, :[]=, :[], :keys
|
22
|
+
|
23
|
+
def merge!(delegating_transformer)
|
24
|
+
delegating_transformer.keys.each do |key|
|
25
|
+
self[key] = delegating_transformer[key]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_object(message, field)
|
30
|
+
transformer_for(field.submsg_name).to_object(message, field)
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_message(object, field)
|
34
|
+
transformer_for(field.submsg_name).to_message(object, field)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def transformer_for(message_name)
|
40
|
+
@transformers[message_name] || @fallback_transformer
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'protip/transformer'
|
2
|
+
require 'protip/transformers/delegating_transformer'
|
3
|
+
|
4
|
+
require 'active_support/time_with_zone'
|
5
|
+
require 'money'
|
6
|
+
|
7
|
+
# Temporary transformer for types that will be moved out to
|
8
|
+
# user extensions.
|
9
|
+
module Protip
|
10
|
+
module Transformers
|
11
|
+
class DeprecatedTransformer < DelegatingTransformer
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
super
|
15
|
+
self['protip.messages.Currency'] = CurrencyTransformer.new
|
16
|
+
self['protip.messages.Money'] = MoneyTransformer.new
|
17
|
+
self['protip.messages.Date'] = DateTransformer.new
|
18
|
+
self['protip.messages.Range'] = RangeTransformer.new
|
19
|
+
self['protip.messages.ActiveSupport.TimeWithZone'] = TimeWithZoneTransformer.new
|
20
|
+
end
|
21
|
+
|
22
|
+
class CurrencyTransformer
|
23
|
+
include Protip::Transformer
|
24
|
+
def to_object(message, field)
|
25
|
+
message.currency_code
|
26
|
+
end
|
27
|
+
def to_message(object, field)
|
28
|
+
field.subtype.msgclass.new(currency_code: object)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class MoneyTransformer
|
33
|
+
include Protip::Transformer
|
34
|
+
def to_object(message, field)
|
35
|
+
::Money.new(message.amount_cents, message.currency.currency_code)
|
36
|
+
end
|
37
|
+
def to_message(object, field)
|
38
|
+
raise ArgumentError unless object.is_a?(::Money)
|
39
|
+
currency = Protip::Messages::Currency.new(currency_code: object.currency.iso_code.to_sym)
|
40
|
+
field.subtype.msgclass.new(
|
41
|
+
amount_cents: object.fractional,
|
42
|
+
currency: currency,
|
43
|
+
)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class DateTransformer
|
48
|
+
include Protip::Transformer
|
49
|
+
def to_object(message, field)
|
50
|
+
::Date.new(message.year, message.month, message.day)
|
51
|
+
end
|
52
|
+
def to_message(object, field)
|
53
|
+
raise ArgumentError unless object.is_a?(::Date)
|
54
|
+
field.subtype.msgclass.new(year: object.year, month: object.month, day: object.day)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class RangeTransformer
|
59
|
+
include Protip::Transformer
|
60
|
+
def to_object(message, field)
|
61
|
+
message.begin..message.end
|
62
|
+
end
|
63
|
+
def to_message(object, field)
|
64
|
+
field.subtype.msgclass.new(begin: object.begin.to_i, end: object.end.to_i)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class TimeWithZoneTransformer
|
69
|
+
include Protip::Transformer
|
70
|
+
def to_object(message, field)
|
71
|
+
ActiveSupport::TimeWithZone.new(
|
72
|
+
Time.at(message.utc_timestamp).utc,
|
73
|
+
ActiveSupport::TimeZone.new(message.time_zone_name)
|
74
|
+
)
|
75
|
+
end
|
76
|
+
def to_message(object, field)
|
77
|
+
if !object.is_a?(::ActiveSupport::TimeWithZone) && (object.is_a?(Time) || object.is_a?(DateTime))
|
78
|
+
object = ::ActiveSupport::TimeWithZone.new(object.to_time.utc, ::ActiveSupport::TimeZone.new('UTC'))
|
79
|
+
end
|
80
|
+
raise ArgumentError unless object.is_a?(::ActiveSupport::TimeWithZone)
|
81
|
+
|
82
|
+
field.subtype.msgclass.new(
|
83
|
+
utc_timestamp: object.to_i,
|
84
|
+
time_zone_name: object.time_zone.name,
|
85
|
+
)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'protip/transformers/delegating_transformer'
|
2
|
+
|
3
|
+
module Protip
|
4
|
+
module Transformers
|
5
|
+
class EnumTransformer < DelegatingTransformer
|
6
|
+
def initialize
|
7
|
+
super
|
8
|
+
self['protip.messages.EnumValue'] = ScalarTransformer.new
|
9
|
+
self['protip.messages.RepeatedEnum'] = ArrayTransformer.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.enum_for_field(field)
|
13
|
+
Google::Protobuf::DescriptorPool.generated_pool.lookup(field.instance_variable_get(:'@_protip_enum_value'))
|
14
|
+
end
|
15
|
+
|
16
|
+
# Internal helper classes - under the hood we use separate transformers for
|
17
|
+
# the scalar and repeated cases, and both of them share transformation logic
|
18
|
+
# by inheriting from +SingleMessageEnumTransformer+.
|
19
|
+
class SingleMessageEnumTransformer
|
20
|
+
include Protip::Transformer
|
21
|
+
private
|
22
|
+
# Instance-level cached version of the class method above
|
23
|
+
def enum_for_field(field)
|
24
|
+
@enum_for_field_cache ||= {}
|
25
|
+
name = EnumTransformer.enum_for_field(field)
|
26
|
+
@enum_for_field_cache[name] ||= EnumTransformer.enum_for_field(field)
|
27
|
+
|
28
|
+
@enum_for_field_cache[name] ||
|
29
|
+
raise("protip_enum missing or invalid for field '#{field.name}'")
|
30
|
+
end
|
31
|
+
|
32
|
+
# Matches the protobuf enum setter behavior.
|
33
|
+
# Convert +:VALUE+ or +5+ to their corresponding enum integer value.
|
34
|
+
# @example
|
35
|
+
# // foo.proto
|
36
|
+
# enum Foo {
|
37
|
+
# BAR = 0;
|
38
|
+
# BAZ = 1;
|
39
|
+
# }
|
40
|
+
# // ScalarTransformer.to_int(:BAZ) # => 1
|
41
|
+
# // ScalarTransformer.to_int(4) # => 4
|
42
|
+
def to_int(symbol_or_int, field)
|
43
|
+
if symbol_or_int.is_a?(Fixnum)
|
44
|
+
symbol_or_int
|
45
|
+
else
|
46
|
+
# Convert +.to_sym+ explicitly to allow strings (or other
|
47
|
+
# symobolizable objects) to be passed in to setters.
|
48
|
+
enum_for_field(field).lookup_name(symbol_or_int.to_sym) ||
|
49
|
+
raise(RangeError.new "unknown symbol value for field '#{field.name}'")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Matches the protobuf enum getter behavior.
|
54
|
+
# Convert integers to their associated enum symbol, or pass them
|
55
|
+
# through if the
|
56
|
+
def to_symbol_or_int(int, field)
|
57
|
+
enum = EnumTransformer.enum_for_field(field) ||
|
58
|
+
raise("protip_enum missing or invalid for field '#{field.name}'")
|
59
|
+
enum.lookup_value(int) || int
|
60
|
+
end
|
61
|
+
end
|
62
|
+
private_constant :SingleMessageEnumTransformer
|
63
|
+
|
64
|
+
# For +protip.messages.EnumValue+
|
65
|
+
class ScalarTransformer < SingleMessageEnumTransformer
|
66
|
+
def to_object(message, field)
|
67
|
+
to_symbol_or_int(message.value, field)
|
68
|
+
end
|
69
|
+
def to_message(object, field)
|
70
|
+
field.subtype.msgclass.new(value: to_int(object, field))
|
71
|
+
end
|
72
|
+
end
|
73
|
+
private_constant :ScalarTransformer
|
74
|
+
|
75
|
+
# For +protip.messages.RepeatedEnum+
|
76
|
+
class ArrayTransformer < SingleMessageEnumTransformer
|
77
|
+
def to_object(message, field)
|
78
|
+
message.values.map do |value|
|
79
|
+
to_symbol_or_int(value, field)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def to_message(object, field)
|
84
|
+
values = (object.is_a?(::Enumerable) ? object : [object]).map do |value|
|
85
|
+
to_int(value, field)
|
86
|
+
end
|
87
|
+
field.subtype.msgclass.new(values: values)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
private_constant :ArrayTransformer
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'protip/transformer'
|
2
|
+
|
3
|
+
require 'protip/transformers/delegating_transformer'
|
4
|
+
|
5
|
+
module Protip
|
6
|
+
module Transformers
|
7
|
+
# Transforms ints, strings, booleans, floats, and bytes to/from their
|
8
|
+
# well-known types (for scalars) and Protip repeated types.
|
9
|
+
class PrimitivesTransformer < DelegatingTransformer
|
10
|
+
TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE', 'on', 'ON']
|
11
|
+
FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE', 'off', 'OFF']
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
super
|
15
|
+
{
|
16
|
+
Int64: :to_i.to_proc,
|
17
|
+
Int32: :to_i.to_proc,
|
18
|
+
UInt64: :to_i.to_proc,
|
19
|
+
UInt32: :to_i.to_proc,
|
20
|
+
Double: :to_f.to_proc,
|
21
|
+
Float: :to_f.to_proc,
|
22
|
+
String: :to_s.to_proc,
|
23
|
+
Bool: ->(object) { to_boolean(object) },
|
24
|
+
Bytes: ->(object) { object },
|
25
|
+
}.each do |type, transform|
|
26
|
+
self["google.protobuf.#{type}Value"] = ScalarTransformer.new(transform)
|
27
|
+
self["protip.messages.Repeated#{type}"] = ArrayTransformer.new(transform)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
def to_boolean(object)
|
33
|
+
return true if TRUE_VALUES.include?(object)
|
34
|
+
return false if FALSE_VALUES.include?(object)
|
35
|
+
|
36
|
+
object
|
37
|
+
end
|
38
|
+
|
39
|
+
# Helper transfomer for scalar well-known types.
|
40
|
+
class ScalarTransformer
|
41
|
+
# @param [Proc] transform Proc to convert a Ruby object to the
|
42
|
+
# primitive type that we're transforming to.
|
43
|
+
def initialize(transform)
|
44
|
+
@transform = transform
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_object(message, field)
|
48
|
+
message.value
|
49
|
+
end
|
50
|
+
|
51
|
+
def to_message(object, field)
|
52
|
+
value = @transform[object]
|
53
|
+
field.subtype.msgclass.new(value: value)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
private_constant :ScalarTransformer
|
57
|
+
|
58
|
+
# Helper transformer for repeated types.
|
59
|
+
class ArrayTransformer
|
60
|
+
def initialize(transform)
|
61
|
+
@transform = transform
|
62
|
+
end
|
63
|
+
|
64
|
+
def to_object(message, field)
|
65
|
+
message.values.to_a.freeze
|
66
|
+
end
|
67
|
+
|
68
|
+
def to_message(object, field)
|
69
|
+
values = (object.is_a?(::Enumerable) ? object : [object]).map do |value|
|
70
|
+
@transform[value]
|
71
|
+
end
|
72
|
+
field.subtype.msgclass.new(values: values)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
private_constant :ArrayTransformer
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|