protip 0.20.7 → 0.30.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|