protip 0.20.7 → 0.30.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/lib/protip.rb +10 -0
  3. data/lib/protip/{wrapper.rb → decorator.rb} +98 -82
  4. data/lib/protip/extensions.rb +0 -1
  5. data/lib/protip/resource.rb +56 -32
  6. data/lib/protip/resource/extra_methods.rb +5 -3
  7. data/lib/protip/tasks/compile.rake +2 -2
  8. data/lib/protip/transformer.rb +14 -0
  9. data/lib/protip/transformers/abstract_transformer.rb +11 -0
  10. data/lib/protip/transformers/active_support/time_with_zone_transformer.rb +35 -0
  11. data/lib/protip/transformers/decorating_transformer.rb +33 -0
  12. data/lib/protip/transformers/default_transformer.rb +22 -0
  13. data/lib/protip/transformers/delegating_transformer.rb +44 -0
  14. data/lib/protip/transformers/deprecated_transformer.rb +90 -0
  15. data/lib/protip/transformers/enum_transformer.rb +93 -0
  16. data/lib/protip/transformers/primitives_transformer.rb +78 -0
  17. data/lib/protip/transformers/timestamp_transformer.rb +31 -0
  18. data/test/functional/protip/decorator_test.rb +204 -0
  19. data/test/unit/protip/decorator_test.rb +665 -0
  20. data/test/unit/protip/resource_test.rb +76 -92
  21. data/test/unit/protip/transformers/decorating_transformer_test.rb +92 -0
  22. data/test/unit/protip/transformers/delegating_transformer_test.rb +83 -0
  23. data/test/unit/protip/transformers/deprecated_transformer_test.rb +139 -0
  24. data/test/unit/protip/transformers/enum_transformer_test.rb +157 -0
  25. data/test/unit/protip/transformers/primitives_transformer_test.rb +139 -0
  26. data/test/unit/protip/transformers/timestamp_transformer_test.rb +46 -0
  27. metadata +23 -12
  28. data/definitions/google/protobuf/wrappers.proto +0 -99
  29. data/lib/google/protobuf/descriptor.rb +0 -1
  30. data/lib/google/protobuf/wrappers.rb +0 -48
  31. data/lib/protip/converter.rb +0 -22
  32. data/lib/protip/standard_converter.rb +0 -185
  33. data/test/unit/protip/standard_converter_test.rb +0 -502
  34. 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::Wrapper.new(response, resource.class.converter)
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::Wrapper.new(response, resource_class.converter)
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 # is there a better way to log this?
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