protip 0.19.2 → 0.20.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7bfa2572b20c366e72093d7c071ae2cbf26302a1
4
- data.tar.gz: 282b0e395f8cc7038f8fc923d3857fd936155fb9
3
+ metadata.gz: 4e9e8d262a3ac9f0a1164c3da1c73895c23eb868
4
+ data.tar.gz: 6898418d57886241026534c0a04174944a2092c2
5
5
  SHA512:
6
- metadata.gz: 86dbb93f457cef7f024e954929299aaec41714db6c9966228d4c499738d16dd6347cfe07ba389c7f52af2fb3d8148ba8896154c1f037d1c1c08dfc27bbfb8a0d
7
- data.tar.gz: 131be596c91854dd6e504be790c4905139f2bae872997bff9ce36f0de539266613b3f2e41f767314475c1a7e852ed138aa3b153a71fa7c9a9dce600f969ecb2e
6
+ metadata.gz: 58bfca0184504abdc837dc766fde21eaa164f02fc22b1b0b3318ffb654bcc909474d0c660234145b8df69de866fc7da86c4c56f8dce71d3dc0769f15d85dbc13
7
+ data.tar.gz: 84502680643b0aac55ffda5e833381e0f20bae12415977b1a0991e0fa564aa16bf0ec4326870d1495e451e16c0de3d511260cb6b6f8568d396f4c12e0057d1db
@@ -0,0 +1,7 @@
1
+ syntax = "proto3";
2
+
3
+ import "google/protobuf/descriptor.proto";
4
+
5
+ extend google.protobuf.FieldOptions {
6
+ string protip_enum = 14383889;
7
+ }
@@ -1,5 +1,7 @@
1
1
  syntax = "proto3";
2
2
 
3
+ import public "protip/extensions.proto";
4
+
3
5
  package protip.messages;
4
6
 
5
7
  message RepeatedDouble {
@@ -36,4 +38,10 @@ message RepeatedString {
36
38
 
37
39
  message RepeatedBytes {
38
40
  repeated bytes values = 1;
41
+ }
42
+
43
+ // Should generally be used with the "protip_enum" option
44
+ // for serialization to/from Ruby.
45
+ message RepeatedEnum {
46
+ repeated int32 values = 1;
39
47
  }
@@ -0,0 +1,18 @@
1
+ // Should be temporary - enables testing our hacky enum option handling with an actual compiled file.
2
+ // Do not depend on anything in this file.
3
+
4
+ syntax = "proto3";
5
+
6
+ import "protip/messages/repeated_wrappers.proto";
7
+ import "protip/messages/wrappers.proto";
8
+
9
+ package protip.messages;
10
+
11
+ message EnumTest {
12
+ enum Enum {
13
+ ZERO = 0;
14
+ ONE = 1;
15
+ }
16
+ protip.messages.EnumValue enum = 1 [(protip_enum) = "protip.messages.EnumTest.Enum"];
17
+ protip.messages.RepeatedEnum enums = 2 [(protip_enum) = "protip.messages.EnumTest.Enum"];
18
+ }
@@ -0,0 +1,9 @@
1
+ syntax = "proto3";
2
+
3
+ import public "protip/extensions.proto";
4
+
5
+ package protip.messages;
6
+
7
+ message EnumValue {
8
+ int32 value = 1;
9
+ }
@@ -0,0 +1 @@
1
+ # Placeholder file until Google rolls google/protobuf/descriptor into the gem
@@ -11,11 +11,11 @@ module Protip
11
11
  )
12
12
  end
13
13
 
14
- def to_object(message)
14
+ def to_object(message, field)
15
15
  raise NotImplementedError.new('Must convert a message into a Ruby object')
16
16
  end
17
17
 
18
- def to_message(object, message_class)
18
+ def to_message(object, message_class, field)
19
19
  raise NotImplementedError.new('Must convert a Ruby object into a message of the given type')
20
20
  end
21
21
  end
@@ -0,0 +1,9 @@
1
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
2
+ # source: protip/extensions.proto
3
+
4
+ require 'google/protobuf'
5
+
6
+ require 'google/protobuf/descriptor'
7
+ Google::Protobuf::DescriptorPool.generated_pool.build do
8
+ end
9
+
@@ -3,6 +3,7 @@
3
3
 
4
4
  require 'google/protobuf'
5
5
 
6
+ require 'protip/extensions'
6
7
  Google::Protobuf::DescriptorPool.generated_pool.build do
7
8
  add_message "protip.messages.RepeatedDouble" do
8
9
  repeated :values, :double, 1
@@ -31,6 +32,9 @@ Google::Protobuf::DescriptorPool.generated_pool.build do
31
32
  add_message "protip.messages.RepeatedBytes" do
32
33
  repeated :values, :bytes, 1
33
34
  end
35
+ add_message "protip.messages.RepeatedEnum" do
36
+ repeated :values, :int32, 1
37
+ end
34
38
  end
35
39
 
36
40
  module Protip
@@ -44,5 +48,6 @@ module Protip
44
48
  RepeatedBool = Google::Protobuf::DescriptorPool.generated_pool.lookup("protip.messages.RepeatedBool").msgclass
45
49
  RepeatedString = Google::Protobuf::DescriptorPool.generated_pool.lookup("protip.messages.RepeatedString").msgclass
46
50
  RepeatedBytes = Google::Protobuf::DescriptorPool.generated_pool.lookup("protip.messages.RepeatedBytes").msgclass
51
+ RepeatedEnum = Google::Protobuf::DescriptorPool.generated_pool.lookup("protip.messages.RepeatedEnum").msgclass
47
52
  end
48
53
  end
@@ -0,0 +1,28 @@
1
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
2
+ # source: protip/messages/test.proto
3
+
4
+ require 'google/protobuf'
5
+
6
+ require 'protip/messages/repeated_wrappers'
7
+ require 'protip/messages/wrappers'
8
+ Google::Protobuf::DescriptorPool.generated_pool.build do
9
+ add_message "protip.messages.EnumTest" do
10
+ optional :enum, :message, 1, "protip.messages.EnumValue"
11
+ optional :enums, :message, 2, "protip.messages.RepeatedEnum"
12
+ end
13
+ add_enum "protip.messages.EnumTest.Enum" do
14
+ value :ZERO, 0
15
+ value :ONE, 1
16
+ end
17
+ end
18
+
19
+ module Protip
20
+ module Messages
21
+ EnumTest = Google::Protobuf::DescriptorPool.generated_pool.lookup("protip.messages.EnumTest").msgclass
22
+ EnumTest::Enum = Google::Protobuf::DescriptorPool.generated_pool.lookup("protip.messages.EnumTest.Enum").enummodule
23
+ end
24
+ end
25
+
26
+ # -- Protip hack until https://github.com/google/protobuf/issues/1198 is resolved
27
+ Google::Protobuf::DescriptorPool.generated_pool.lookup("protip.messages.EnumTest").lookup("enum").instance_variable_set(:"@_protip_enum_value", "protip.messages.EnumTest.Enum")
28
+ Google::Protobuf::DescriptorPool.generated_pool.lookup("protip.messages.EnumTest").lookup("enums").instance_variable_set(:"@_protip_enum_value", "protip.messages.EnumTest.Enum")
@@ -0,0 +1,17 @@
1
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
2
+ # source: protip/messages/wrappers.proto
3
+
4
+ require 'google/protobuf'
5
+
6
+ require 'protip/extensions'
7
+ Google::Protobuf::DescriptorPool.generated_pool.build do
8
+ add_message "protip.messages.EnumValue" do
9
+ optional :value, :int32, 1
10
+ end
11
+ end
12
+
13
+ module Protip
14
+ module Messages
15
+ EnumValue = Google::Protobuf::DescriptorPool.generated_pool.lookup("protip.messages.EnumValue").msgclass
16
+ end
17
+ end
@@ -9,6 +9,7 @@ require 'protip/messages/money'
9
9
  require 'protip/messages/range'
10
10
  require 'protip/messages/repeated_wrappers'
11
11
  require 'protip/messages/types'
12
+ require 'protip/messages/wrappers'
12
13
  require 'google/protobuf'
13
14
  module Protip
14
15
  class StandardConverter
@@ -24,15 +25,15 @@ module Protip
24
25
 
25
26
  ## Protip types
26
27
  @conversions['protip.messages.Currency'] = {
27
- to_object: ->(message) { message.currency_code },
28
- to_message: ->(currency_code, message_class) { message_class.new currency_code: currency_code }
28
+ to_object: ->(message, field) { message.currency_code },
29
+ to_message: ->(currency_code, message_class, field) { message_class.new currency_code: currency_code }
29
30
  }
30
31
 
31
32
  @conversions['protip.messages.Money'] = {
32
- to_object: ->(message) do
33
+ to_object: ->(message, field) do
33
34
  ::Money.new(message.amount_cents, message.currency.currency_code)
34
35
  end,
35
- to_message: ->(money, message_class) do
36
+ to_message: ->(money, message_class, field) do
36
37
  raise ArgumentError unless money.is_a?(::Money)
37
38
  currency = ::Protip::Messages::Currency.new(currency_code: money.currency.iso_code.to_sym)
38
39
  message_class.new(
@@ -43,16 +44,16 @@ module Protip
43
44
  }
44
45
 
45
46
  @conversions['protip.messages.Date'] = {
46
- to_object: ->(message) { ::Date.new(message.year, message.month, message.day) },
47
- to_message: lambda do |date, message_class|
47
+ to_object: ->(message, field) { ::Date.new(message.year, message.month, message.day) },
48
+ to_message: lambda do |date, message_class, field|
48
49
  raise ArgumentError unless date.is_a?(::Date)
49
50
  message_class.new year: date.year, month: date.month, day: date.day
50
51
  end
51
52
  }
52
53
 
53
54
  @conversions['protip.messages.Range'] = {
54
- to_object: ->(message) { message.begin..message.end },
55
- to_message: ->(range, message_class) do
55
+ to_object: ->(message, field) { message.begin..message.end },
56
+ to_message: ->(range, message_class, field) do
56
57
  message_class.new(begin: range.begin.to_i, end: range.end.to_i)
57
58
  end
58
59
  }
@@ -60,43 +61,76 @@ module Protip
60
61
  ## Standard wrappers
61
62
  %w(Int64 Int32 UInt64 UInt32 Double Float String Bytes).each do |type|
62
63
  @conversions["google.protobuf.#{type}Value"] = {
63
- to_object: ->(message) { message.value },
64
- to_message: lambda do |value, message_class|
64
+ to_object: ->(message, field) { message.value },
65
+ to_message: lambda do |value, message_class, field|
65
66
  message_class.new value: value
66
67
  end
67
68
  }
68
69
  @conversions["protip.messages.Repeated#{type}"] = {
69
- to_object: ->(message) { message.values.to_a.freeze },
70
- to_message: lambda do |value, message_class|
70
+ to_object: ->(message, field) { message.values.to_a.freeze },
71
+ to_message: lambda do |value, message_class, field|
71
72
  message_class.new values: (value.is_a?(Enumerable) ? value : [value])
72
73
  end
73
74
  }
74
75
  end
75
76
 
76
77
  conversions['google.protobuf.BoolValue'] = {
77
- to_object: ->(message) { message.value },
78
- to_message: lambda do |value, message_class|
78
+ to_object: ->(message, field) { message.value },
79
+ to_message: lambda do |value, message_class, field|
79
80
  message_class.new value: value_to_boolean(value)
80
81
  end
81
82
  }
82
83
 
83
84
  conversions['protip.messages.RepeatedBool'] = {
84
- to_object: ->(message) { message.values.to_a.freeze },
85
- to_message: lambda do |value, message_class|
85
+ to_object: ->(message, field) { message.values.to_a.freeze },
86
+ to_message: lambda do |value, message_class, field|
86
87
  message_class.new values: (value.is_a?(Enumerable) ? value : [value]).map{|v| value_to_boolean(v)}
87
88
  end
88
89
 
89
90
  }
90
91
 
92
+ conversions['protip.messages.EnumValue'] = {
93
+ to_object: lambda do |message, field|
94
+ enum_for_field(field).lookup_value(message.value) || message.value
95
+ end,
96
+ to_message: lambda do |value, message_class, field|
97
+ enum_value = (value.is_a?(Fixnum) ? value : enum_for_field(field).lookup_name(value.to_sym))
98
+ if nil == enum_value
99
+ raise RangeError.new('Unknown symbol value for an EnumValue field.')
100
+ end
101
+ message_class.new(value: enum_value)
102
+ end
103
+ }
104
+
105
+ conversions['protip.messages.RepeatedEnum'] = {
106
+ to_object: lambda do |message, field|
107
+ enum = enum_for_field(field)
108
+ message.values.map{|v| enum.lookup_value(v) || v}
109
+ end,
110
+ to_message: lambda do |values, message_class, field|
111
+ enum = nil # Don't look this up unless we have to
112
+ (values = [values]) unless values.is_a?(Enumerable) # Convert to an array if a scalar was given
113
+ # Convert values in the same way we do for +protip.messages.EnumValue+
114
+ message_class.new values: (values.map do |value|
115
+ if value.is_a?(Fixnum)
116
+ value
117
+ else
118
+ enum ||= enum_for_field(field)
119
+ enum.lookup_name(value.to_sym) || (raise RangeError.new('Unknown symbol value for a RepeatedEnum field.'))
120
+ end
121
+ end)
122
+ end
123
+ }
124
+
91
125
  ## ActiveSupport objects
92
126
  conversions['protip.messages.ActiveSupport.TimeWithZone'] = {
93
- to_object: ->(message) {
127
+ to_object: ->(message, field) {
94
128
  ActiveSupport::TimeWithZone.new(
95
129
  Time.at(message.utc_timestamp).utc,
96
130
  ActiveSupport::TimeZone.new(message.time_zone_name)
97
131
  )
98
132
  },
99
- to_message: ->(value, message_class) {
133
+ to_message: ->(value, message_class, field) {
100
134
  if !value.is_a?(::ActiveSupport::TimeWithZone) && (value.is_a?(Time) || value.is_a?(DateTime))
101
135
  value = ::ActiveSupport::TimeWithZone.new(value.to_time.utc, ::ActiveSupport::TimeZone.new('UTC'))
102
136
  end
@@ -113,12 +147,12 @@ module Protip
113
147
  self.class.conversions.has_key?(message_class.descriptor.name)
114
148
  end
115
149
 
116
- def to_object(message)
117
- self.class.conversions[message.class.descriptor.name][:to_object].call(message)
150
+ def to_object(message, field)
151
+ self.class.conversions[message.class.descriptor.name][:to_object].call(message, field)
118
152
  end
119
153
 
120
- def to_message(object, message_class)
121
- self.class.conversions[message_class.descriptor.name][:to_message].call(object, message_class)
154
+ def to_message(object, message_class, field)
155
+ self.class.conversions[message_class.descriptor.name][:to_message].call(object, message_class, field)
122
156
  end
123
157
 
124
158
  class << self
@@ -130,6 +164,11 @@ module Protip
130
164
  # exception)
131
165
  value
132
166
  end
167
+
168
+ private
169
+ def enum_for_field(field)
170
+ Google::Protobuf::DescriptorPool.generated_pool.lookup(field.instance_variable_get(:'@_protip_enum_value'))
171
+ end
133
172
  end
134
173
  end
135
174
  end
@@ -187,7 +187,7 @@ module Protip
187
187
  if nil == value
188
188
  nil
189
189
  elsif converter.convertible?(field.subtype.msgclass)
190
- converter.to_object value
190
+ converter.to_object value, field
191
191
  elsif nested_resources.has_key?(field_name_sym)
192
192
  resource_klass = nested_resources[field_name_sym]
193
193
  resource_klass.new value
@@ -217,7 +217,7 @@ module Protip
217
217
  elsif value.is_a?(field.subtype.msgclass)
218
218
  value
219
219
  elsif converter.convertible?(field.subtype.msgclass)
220
- converter.to_message value, field.subtype.msgclass
220
+ converter.to_message value, field.subtype.msgclass, field
221
221
  elsif nested_resources.has_key?(field.name.to_sym)
222
222
  value.message
223
223
  else
data/test/test_helper.rb CHANGED
@@ -2,6 +2,5 @@ require 'minitest/autorun'
2
2
  require 'mocha/mini_test'
3
3
  require 'webmock/minitest'
4
4
  require 'minitest/stub/const'
5
- require 'minitest/debugger' if ENV['DEBUG']
6
5
 
7
6
  require 'minitest/pride'
@@ -63,6 +63,7 @@ module Protip::ResourceTest # Namespace for internal constants
63
63
  pool.lookup(name).msgclass
64
64
  end
65
65
  end
66
+ let(:nested_message_field) { resource_message_class.descriptor.lookup('nested_message') }
66
67
  # Stubbed API client
67
68
  let :client do
68
69
  mock.responds_like_instance_of(Class.new { include Protip::Client })
@@ -158,8 +159,8 @@ module Protip::ResourceTest # Namespace for internal constants
158
159
 
159
160
  it 'converts message types to and from their Ruby values when the converter allows' do
160
161
  converter.expects(:convertible?).at_least_once.with(nested_message_class).returns(true)
161
- converter.expects(:to_message).once.with(6, nested_message_class).returns(nested_message_class.new number: 100)
162
- converter.expects(:to_object).at_least_once.with(nested_message_class.new number: 100).returns 'intern'
162
+ converter.expects(:to_message).once.with(6, nested_message_class, nested_message_field).returns(nested_message_class.new number: 100)
163
+ converter.expects(:to_object).at_least_once.with(nested_message_class.new(number: 100), nested_message_field).returns 'intern'
163
164
 
164
165
  resource = resource_class.new
165
166
  resource.nested_message = 6
@@ -243,8 +244,8 @@ module Protip::ResourceTest # Namespace for internal constants
243
244
  before do
244
245
  # Sanity check - the user should specify all these variables in "let" statements
245
246
  # http_method, path, query_class, and response specify the expected call to the client
246
- # nested_message_field specifies the field on the query class that may or may not be convertible, and should
247
- # refer to a submessage field of type nested_message_class
247
+ # nested_message_field_name specifies the field on the query class that may or may not be convertible, and
248
+ # should refer to a submessage field of type nested_message_class
248
249
  # invoke_method! should call the desired method, assuming that +parameters+ contains the query parameters to
249
250
  # pass in (e.g. `resource_class.all(parameters)` or `resource_class.find('id', parameters)`)
250
251
  %i(
@@ -252,7 +253,7 @@ module Protip::ResourceTest # Namespace for internal constants
252
253
  path
253
254
  query_class
254
255
  response
255
- nested_message_field
256
+ nested_message_field_name
256
257
  invoke_method!
257
258
  ).each do |name|
258
259
  raise "Must define #{name} before invoking `it_converts_query_parameters`" unless respond_to?(name)
@@ -262,7 +263,7 @@ module Protip::ResourceTest # Namespace for internal constants
262
263
  client.expects(:request)
263
264
  .once
264
265
  .with(method: http_method, path: path,
265
- message: query_class.new(:"#{nested_message_field}" => nested_message_class.new(number: 43)),
266
+ message: query_class.new(:"#{nested_message_field_name}" => nested_message_class.new(number: 43)),
266
267
  response_type: (nil == response ? nil : response.class),
267
268
  ).returns(response)
268
269
  end
@@ -272,10 +273,12 @@ module Protip::ResourceTest # Namespace for internal constants
272
273
  describe 'with a convertible message' do
273
274
  before do
274
275
  resource_class.converter.stubs(:convertible?).with(nested_message_class).returns(true)
275
- resource_class.converter.stubs(:to_message).with(42, nested_message_class).returns(nested_message_class.new(number: 43))
276
+ resource_class.converter.stubs(:to_message)
277
+ .with(42, nested_message_class, query_class.descriptor.lookup(nested_message_field_name.to_s))
278
+ .returns(nested_message_class.new(number: 43))
276
279
  end
277
280
 
278
- let(:parameters) { {"#{nested_message_field}" => 42} }
281
+ let(:parameters) { {"#{nested_message_field_name}" => 42} }
279
282
  it 'converts query parameters' do
280
283
  invoke_method!
281
284
  end
@@ -288,14 +291,14 @@ module Protip::ResourceTest # Namespace for internal constants
288
291
  end
289
292
 
290
293
  describe 'with a hash' do
291
- let(:parameters) { {"#{nested_message_field}" => {number: 43}} }
294
+ let(:parameters) { {"#{nested_message_field_name}" => {number: 43}} }
292
295
  it 'allows a hash to be provided for the nested message' do
293
296
  invoke_method!
294
297
  end
295
298
  end
296
299
 
297
300
  describe 'with a submessage' do
298
- let(:parameters) { {"#{nested_message_field}" => nested_message_class.new(number: 43)} }
301
+ let(:parameters) { {"#{nested_message_field_name}" => nested_message_class.new(number: 43)} }
299
302
  it 'allows a submessage to be provided directly' do
300
303
  invoke_method!
301
304
  end
@@ -398,7 +401,7 @@ module Protip::ResourceTest # Namespace for internal constants
398
401
  let(:http_method) { Net::HTTP::Get }
399
402
  let(:path) { 'base_path' }
400
403
  let(:query_class) { resource_query_class }
401
- let(:nested_message_field) { :nested_message }
404
+ let(:nested_message_field_name) { :nested_message }
402
405
  let(:invoke_method!) { resource_class.all(parameters) }
403
406
  it_converts_query_parameters
404
407
  end
@@ -482,7 +485,7 @@ module Protip::ResourceTest # Namespace for internal constants
482
485
  let(:http_method) { Net::HTTP::Get }
483
486
  let(:path) { 'base_path/5' }
484
487
  let(:query_class) { resource_query_class }
485
- let(:nested_message_field) { :nested_message }
488
+ let(:nested_message_field_name) { :nested_message }
486
489
  let(:invoke_method!) { resource_class.find 5, parameters }
487
490
  it_converts_query_parameters
488
491
  end
@@ -571,9 +574,9 @@ module Protip::ResourceTest # Namespace for internal constants
571
574
  describe '(message attributes)' do
572
575
  before do
573
576
  converter.stubs(:convertible?).with(nested_message_class).returns(true)
574
- converter.stubs(:to_message).with(42, nested_message_class).returns(nested_message_class.new(number: 52))
575
- converter.stubs(:to_object).with(nested_message_class.new(number: 52)).returns(42)
576
- converter.stubs(:to_object).with(nested_message_class.new(number: 62)).returns(72)
577
+ converter.stubs(:to_message).with(42, nested_message_class, nested_message_field).returns(nested_message_class.new(number: 52))
578
+ converter.stubs(:to_object).with(nested_message_class.new(number: 52), nested_message_field).returns(42)
579
+ converter.stubs(:to_object).with(nested_message_class.new(number: 62), nested_message_field).returns(72)
577
580
  end
578
581
  it 'marks convertible messages as changed if they are changed as Ruby values' do
579
582
  setter.set nested_message: 42
@@ -937,7 +940,7 @@ module Protip::ResourceTest # Namespace for internal constants
937
940
  let(:http_method) { Net::HTTP::Post }
938
941
  let(:path) { path }
939
942
  let(:query_class) { action_query_class }
940
- let(:nested_message_field) { :nested_message }
943
+ let(:nested_message_field_name) { :nested_message }
941
944
  let(:invoke_method!) { target.action(parameters) }
942
945
  it_converts_query_parameters
943
946
  end
@@ -4,8 +4,23 @@ require 'money'
4
4
  require 'google/protobuf/wrappers'
5
5
  require 'protip/messages/active_support/time_with_zone'
6
6
  require 'protip/standard_converter'
7
+ require 'protip/messages/test'
7
8
 
8
9
  describe Protip::StandardConverter do
10
+ let :pool do
11
+ # See https://github.com/google/protobuf/blob/master/ruby/tests/generated_code.rb for
12
+ # examples of field types you can add here
13
+ pool = Google::Protobuf::DescriptorPool.new
14
+ pool.build do
15
+ add_enum 'number' do
16
+ value :ZERO, 0
17
+ value :ONE, 1
18
+ end
19
+ end
20
+ pool
21
+ end
22
+ let(:enum) { pool.lookup 'number' }
23
+
9
24
  let(:converter) { Protip::StandardConverter.new }
10
25
 
11
26
  let(:integer_types) do
@@ -94,6 +109,8 @@ describe Protip::StandardConverter do
94
109
  end
95
110
  end
96
111
 
112
+ let(:field) { mock.responds_like_instance_of Google::Protobuf::FieldDescriptor }
113
+
97
114
  describe '#to_object' do
98
115
  it 'converts wrapper types' do
99
116
  {
@@ -104,7 +121,7 @@ describe Protip::StandardConverter do
104
121
  bytes_value => bytes_types,
105
122
  }.each do |value, message_types|
106
123
  message_types.each do |message_class|
107
- assert_equal value, converter.to_object(message_class.new value: value)
124
+ assert_equal value, converter.to_object(message_class.new(value: value), field)
108
125
  end
109
126
  end
110
127
  end
@@ -118,7 +135,7 @@ describe Protip::StandardConverter do
118
135
  bytes_value => repeated_bytes_types,
119
136
  }.each do |value, message_types|
120
137
  message_types.each do |message_class|
121
- result = converter.to_object(message_class.new values: [value])
138
+ result = converter.to_object(message_class.new(values: [value]), field)
122
139
  assert_equal [value], result
123
140
  exception = assert_raises RuntimeError do
124
141
  result << value
@@ -129,26 +146,26 @@ describe Protip::StandardConverter do
129
146
  end
130
147
 
131
148
  it 'converts dates' do
132
- date = converter.to_object(::Protip::Messages::Date.new year: 2015, month: 2, day: 9)
149
+ date = converter.to_object(::Protip::Messages::Date.new(year: 2015, month: 2, day: 9), field)
133
150
  assert_instance_of ::Date, date
134
151
  assert_equal '2015-02-09', date.strftime
135
152
  end
136
153
 
137
154
  it 'converts ranges' do
138
- range = converter.to_object(::Protip::Messages::Range.new begin: 1, end: 4)
155
+ range = converter.to_object(::Protip::Messages::Range.new(begin: 1, end: 4), field)
139
156
  assert_instance_of ::Range, range
140
157
  assert_equal 1..4, range
141
158
  end
142
159
 
143
160
  it 'converts currency' do
144
- currency = converter.to_object(::Protip::Messages::Currency.new currency_code: :GBP)
161
+ currency = converter.to_object(::Protip::Messages::Currency.new(currency_code: :GBP), field)
145
162
  assert_equal :GBP, currency
146
163
  end
147
164
 
148
165
  it 'converts money' do
149
166
  message = ::Protip::Messages::Money.new amount_cents: 250,
150
167
  currency: (::Protip::Messages::Currency.new currency_code: :CAD)
151
- money = converter.to_object(message)
168
+ money = converter.to_object(message, field)
152
169
  assert_instance_of ::Money, money
153
170
  assert_equal Money::Currency.new(:CAD), money.currency
154
171
  assert_equal 250, money.fractional
@@ -158,11 +175,42 @@ describe Protip::StandardConverter do
158
175
  it 'converts times with zones' do
159
176
  message = ::Protip::Messages::ActiveSupport::TimeWithZone.new utc_timestamp: 1451610000,
160
177
  time_zone_name: 'America/Los_Angeles'
161
- time = converter.to_object(message)
178
+ time = converter.to_object(message, field)
162
179
  assert_instance_of ::ActiveSupport::TimeWithZone, time
163
180
  assert_equal 1451610000, time.to_i
164
181
  assert_equal '2015-12-31T17:00:00-08:00', time.iso8601
165
182
  end
183
+
184
+ describe 'enums' do
185
+ before do
186
+ ::Protip::StandardConverter.stubs(:enum_for_field).with(field).returns(enum)
187
+ end
188
+
189
+ # Make sure we mirror the behavior of an actual enum field on the message.
190
+ it 'converts enum values in range to symbols' do
191
+ message = ::Protip::Messages::EnumValue.new value: 1
192
+ assert_equal :ONE, converter.to_object(message, field)
193
+ end
194
+
195
+ it 'converts enum values out of range to integers' do
196
+ message = ::Protip::Messages::EnumValue.new value: 5
197
+ assert_equal 5, converter.to_object(message, field)
198
+ end
199
+
200
+ it 'converts repeated enum values in range to symbols' do
201
+ message = ::Protip::Messages::RepeatedEnum.new values: [0, 1]
202
+ assert_equal [:ZERO, :ONE], converter.to_object(message, field)
203
+ end
204
+
205
+ it 'converts repeated enum values out of range to integers' do
206
+ message = ::Protip::Messages::RepeatedEnum.new values: [3, 1, 5]
207
+ assert_equal [3, :ONE, 5], converter.to_object(message, field)
208
+ end
209
+ end
210
+ end
211
+
212
+ describe '.enum_for_field' do
213
+ # TODO pending https://github.com/google/protobuf/issues/1198
166
214
  end
167
215
 
168
216
  describe '#to_message' do
@@ -175,7 +223,7 @@ describe Protip::StandardConverter do
175
223
  bytes_value => bytes_types,
176
224
  }.each do |value, message_types|
177
225
  message_types.each do |message_class|
178
- assert_equal message_class.new(value: value), converter.to_message(value, message_class)
226
+ assert_equal message_class.new(value: value), converter.to_message(value, message_class, field)
179
227
  end
180
228
  end
181
229
  end
@@ -189,7 +237,7 @@ describe Protip::StandardConverter do
189
237
  bytes_value => repeated_bytes_types,
190
238
  }.each do |value, message_types|
191
239
  message_types.each do |message_class|
192
- assert_equal message_class.new(values: [value]), converter.to_message(value, message_class)
240
+ assert_equal message_class.new(values: [value]), converter.to_message(value, message_class, field)
193
241
  end
194
242
  end
195
243
  end
@@ -203,7 +251,9 @@ describe Protip::StandardConverter do
203
251
  bytes_value => repeated_bytes_types,
204
252
  }.each do |value, message_types|
205
253
  message_types.each do |message_class|
206
- assert_equal message_class.new(values: [value, value]), converter.to_message([value, value], message_class)
254
+ assert_equal message_class.new(values: [value, value]), converter.to_message(
255
+ [value, value], message_class, field
256
+ )
207
257
  end
208
258
  end
209
259
  end
@@ -211,23 +261,23 @@ describe Protip::StandardConverter do
211
261
  it 'converts dates' do
212
262
  date = ::Date.new(2012, 5, 7)
213
263
  assert_equal 7, date.day # Sanity check argument order
214
- assert_equal ::Protip::Messages::Date.new(year: 2012, month: 5, day: 7), converter.to_message(date, ::Protip::Messages::Date)
264
+ assert_equal ::Protip::Messages::Date.new(year: 2012, month: 5, day: 7), converter.to_message(date, ::Protip::Messages::Date, field)
215
265
  end
216
266
 
217
267
  it 'converts ranges' do
218
268
  range = -1..34
219
- assert_equal ::Protip::Messages::Range.new(begin: -1, end: 34), converter.to_message(range, ::Protip::Messages::Range)
269
+ assert_equal ::Protip::Messages::Range.new(begin: -1, end: 34), converter.to_message(range, ::Protip::Messages::Range, field)
220
270
  end
221
271
 
222
272
  it 'converts currency' do
223
273
  currency = :HKD
224
- message = converter.to_message(currency, ::Protip::Messages::Currency)
274
+ message = converter.to_message(currency, ::Protip::Messages::Currency, field)
225
275
  assert_equal ::Protip::Messages::Currency.new(currency_code: currency), message
226
276
  end
227
277
 
228
278
  it 'converts money' do
229
279
  money = ::Money.new(250, 'CAD')
230
- message = converter.to_message(money, ::Protip::Messages::Money)
280
+ message = converter.to_message(money, ::Protip::Messages::Money, field)
231
281
  assert_instance_of ::Protip::Messages::Money, message
232
282
  assert_equal ::Protip::Messages::Money.new(
233
283
  amount_cents: money.cents,
@@ -240,21 +290,21 @@ describe Protip::StandardConverter do
240
290
  it 'converts truthy values to booleans' do
241
291
  [true, 1, '1', 't', 'T', 'true', 'TRUE', 'on', 'ON'].each do |truth_value|
242
292
  assert_equal Google::Protobuf::BoolValue.new(value: true),
243
- converter.to_message(truth_value, Google::Protobuf::BoolValue)
293
+ converter.to_message(truth_value, Google::Protobuf::BoolValue, field)
244
294
  end
245
295
  end
246
296
 
247
297
  it 'converts falsey values to booleans' do
248
298
  [false, 0, '0', 'f', 'F', 'false', 'FALSE', 'off', 'OFF'].each do |false_value|
249
299
  assert_equal Google::Protobuf::BoolValue.new(value: false),
250
- converter.to_message(false_value, Google::Protobuf::BoolValue)
300
+ converter.to_message(false_value, Google::Protobuf::BoolValue, field)
251
301
  end
252
302
  end
253
303
 
254
304
  it 'raises an exception if non-boolean values passed to boolean field' do
255
305
  [nil, 'test', Object.new, 2, {}, []].each do |bad_value|
256
306
  assert_raises TypeError do
257
- converter.to_message(bad_value, Google::Protobuf::BoolValue)
307
+ converter.to_message(bad_value, Google::Protobuf::BoolValue, field)
258
308
  end
259
309
  end
260
310
  end
@@ -262,24 +312,119 @@ describe Protip::StandardConverter do
262
312
  it 'converts times with zones' do
263
313
  time_with_zone = ::ActiveSupport::TimeWithZone.new(Time.new(2016, 1, 1, 0, 0, 0, 0),
264
314
  ::ActiveSupport::TimeZone.new('America/New_York'))
265
- message = converter.to_message(time_with_zone, ::Protip::Messages::ActiveSupport::TimeWithZone)
315
+ message = converter.to_message(time_with_zone, ::Protip::Messages::ActiveSupport::TimeWithZone, field)
266
316
  assert_equal 1451606400, message.utc_timestamp
267
317
  assert_equal 'America/New_York', message.time_zone_name
268
318
  end
269
319
 
270
320
  it 'converts times without zones' do
271
321
  time = Time.new(2016, 1, 1, 0, 0, 0, -3600)
272
- message = converter.to_message(time, ::Protip::Messages::ActiveSupport::TimeWithZone)
322
+ message = converter.to_message(time, ::Protip::Messages::ActiveSupport::TimeWithZone, field)
273
323
  assert_equal 1451610000, message.utc_timestamp
274
324
  assert_equal 'UTC', message.time_zone_name
275
325
  end
276
326
 
277
327
  it 'converts datetimes without zones' do
278
328
  datetime = DateTime.new(2016, 1, 1, 0, 0, 0, '-1')
279
- message = converter.to_message(datetime, ::Protip::Messages::ActiveSupport::TimeWithZone)
329
+ message = converter.to_message(datetime, ::Protip::Messages::ActiveSupport::TimeWithZone, field)
280
330
  assert_equal 1451610000, message.utc_timestamp
281
331
  assert_equal 'UTC', message.time_zone_name
282
332
 
283
333
  end
334
+
335
+ describe 'enums' do
336
+ before do
337
+ ::Protip::StandardConverter.stubs(:enum_for_field).with(field).returns(enum)
338
+ end
339
+ def convert(value)
340
+ converter.to_message value, message_class, field
341
+ end
342
+ %w(zero one two).each do |number| # values symbolizing as :ZERO, :ONE, :TWO
343
+ let number do
344
+ value = mock
345
+ value.stubs(:to_sym).returns(number.upcase.to_sym)
346
+ value
347
+ end
348
+ end
349
+
350
+ describe '::Protip::Messages::EnumValue' do
351
+ let(:message_class) { ::Protip::Messages::EnumValue }
352
+ # Ensure identical behavior to setting a standard enum field
353
+ it 'converts integers' do
354
+ assert_equal 1, convert(1).value, 'improper conversion of an in-range integer'
355
+ assert_equal 4, convert(4).value, 'improper conversion of an out-of-range integer'
356
+ end
357
+ it 'converts non-integers via to_sym' do
358
+ assert_equal 1, convert(one).value, 'improper conversion of non-integer value'
359
+ end
360
+ it 'throws an error when a non-existent symbol is given' do
361
+ assert_raises RangeError do
362
+ convert(two)
363
+ end
364
+ end
365
+ end
366
+
367
+ describe '::Protip::Messages::RepeatedEnum' do
368
+ let(:message_class) { ::Protip::Messages::RepeatedEnum }
369
+ it 'converts integers' do
370
+ assert_equal [1, 4], convert([1, 4]).values
371
+ end
372
+ it 'converts non-integers via to_sym' do
373
+ assert_equal [0, 2, 1], convert([zero, 2, one]).values
374
+ end
375
+ it 'throws an error when a non-existent symbol is given' do
376
+ assert_raises RangeError do
377
+ convert([0, two])
378
+ end
379
+ end
380
+ it 'allows assigning a scalar value' do
381
+ assert_equal [1], convert(one).values
382
+ end
383
+ end
384
+ end
284
385
  end
386
+
387
+ describe '(enums - functional)' do # Temp - test an actual compiled file to make sure our options hack is working
388
+ let(:wrapped_message) { Protip::Messages::EnumTest.new }
389
+ let(:wrapper) { Protip::Wrapper.new wrapped_message, converter }
390
+
391
+ let(:value_map) do
392
+ {
393
+ :ONE => :ONE,
394
+ 1 => :ONE,
395
+ 2 => 2,
396
+ }
397
+ end
398
+
399
+ it 'allows setting and getting a scalar field by Ruby value' do
400
+ value_map.each do |value, expected|
401
+ wrapper.enum = value
402
+ assert_equal expected, wrapper.enum
403
+ end
404
+ assert_raises RangeError do
405
+ wrapper.enum = :TWO
406
+ end
407
+ end
408
+ it 'allows setting and getting a scalar field by message' do
409
+ wrapper.enum = ::Protip::Messages::EnumValue.new(value: 1)
410
+ assert_equal :ONE, wrapper.enum
411
+ end
412
+
413
+ it 'allows setting and getting a repeated field by Ruby value' do
414
+ value_map.each do |value, expected|
415
+ wrapper.enums = [value]
416
+ assert_equal [expected], wrapper.enums
417
+ end
418
+ assert_raises RangeError do
419
+ wrapper.enums = [:TWO]
420
+ end
421
+ end
422
+ it 'allows setting and geting a repeated field by message' do
423
+ wrapper.enums = ::Protip::Messages::RepeatedEnum.new(values: [2])
424
+ assert_equal [2], wrapper.enums
425
+ end
426
+
427
+
428
+ end
429
+
285
430
  end
@@ -59,6 +59,7 @@ module Protip::WrapperTest # namespace for internal constants
59
59
  pool.lookup(name).msgclass
60
60
  end
61
61
  end
62
+ let (:inner_message_field) { message_class.descriptor.lookup('inner') }
62
63
 
63
64
  # Stubbed API client
64
65
  let :client do
@@ -209,7 +210,7 @@ module Protip::WrapperTest # namespace for internal constants
209
210
  end
210
211
 
211
212
  it 'converts scalar Ruby values to protobuf messages' do
212
- converter.expects(:to_message).once.with(45, inner_message_class).returns(inner_message_class.new(value: 43))
213
+ converter.expects(:to_message).once.with(45, inner_message_class, inner_message_field).returns(inner_message_class.new(value: 43))
213
214
  wrapper.assign_attributes inner: 45
214
215
  assert_equal inner_message_class.new(value: 43), wrapped_message.inner
215
216
  end
@@ -329,7 +330,7 @@ module Protip::WrapperTest # namespace for internal constants
329
330
  describe 'with a nested convertible message' do
330
331
  before do
331
332
  converter.stubs(:convertible?).with(inner_message_class).returns true
332
- [1, 2, 3].each{|i| converter.stubs(:to_object).with(inner_message_class.new(value: i)).returns(i)}
333
+ [1, 2, 3].each{|i| converter.stubs(:to_object).with(inner_message_class.new(value: i), anything).returns(i)}
333
334
  end
334
335
  it 'returns a hash with the nested message converted' do
335
336
  assert_equal 1, wrapper.to_h[:inner]
@@ -385,7 +386,7 @@ module Protip::WrapperTest # namespace for internal constants
385
386
 
386
387
  it 'converts convertible messages' do
387
388
  converter.expects(:convertible?).with(inner_message_class).once.returns(true)
388
- converter.expects(:to_object).once.with(inner_message_class.new(value: 25)).returns 40
389
+ converter.expects(:to_object).once.with(inner_message_class.new(value: 25), inner_message_field).returns 40
389
390
  assert_equal 40, wrapper.inner
390
391
  end
391
392
 
@@ -445,7 +446,7 @@ module Protip::WrapperTest # namespace for internal constants
445
446
 
446
447
  it 'converts convertible messages' do
447
448
  converter.expects(:convertible?).at_least_once.with(inner_message_class).returns(true)
448
- converter.expects(:to_message).with(40, inner_message_class).returns(inner_message_class.new(value: 30))
449
+ converter.expects(:to_message).with(40, inner_message_class, inner_message_field).returns(inner_message_class.new(value: 30))
449
450
 
450
451
  wrapper.inner = 40
451
452
  assert_equal inner_message_class.new(value: 30), wrapper.message.inner
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: protip
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.19.2
4
+ version: 0.20.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - AngelList
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-03-08 00:00:00.000000000 Z
11
+ date: 2016-03-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -98,20 +98,6 @@ dependencies:
98
98
  - - "~>"
99
99
  - !ruby/object:Gem::Version
100
100
  version: '5.0'
101
- - !ruby/object:Gem::Dependency
102
- name: minitest-debugger
103
- requirement: !ruby/object:Gem::Requirement
104
- requirements:
105
- - - "~>"
106
- - !ruby/object:Gem::Version
107
- version: '1.0'
108
- type: :development
109
- prerelease: false
110
- version_requirements: !ruby/object:Gem::Requirement
111
- requirements:
112
- - - "~>"
113
- - !ruby/object:Gem::Version
114
- version: '1.0'
115
101
  - !ruby/object:Gem::Dependency
116
102
  name: minitest-stub-const
117
103
  requirement: !ruby/object:Gem::Requirement
@@ -193,6 +179,7 @@ extensions: []
193
179
  extra_rdoc_files: []
194
180
  files:
195
181
  - definitions/google/protobuf/wrappers.proto
182
+ - definitions/protip/extensions.proto
196
183
  - definitions/protip/messages/active_support/time_with_zone.proto
197
184
  - definitions/protip/messages/array.proto
198
185
  - definitions/protip/messages/currency.proto
@@ -200,12 +187,16 @@ files:
200
187
  - definitions/protip/messages/money.proto
201
188
  - definitions/protip/messages/range.proto
202
189
  - definitions/protip/messages/repeated_wrappers.proto
190
+ - definitions/protip/messages/test.proto
203
191
  - definitions/protip/messages/types.proto
192
+ - definitions/protip/messages/wrappers.proto
193
+ - lib/google/protobuf/descriptor.rb
204
194
  - lib/google/protobuf/wrappers.rb
205
195
  - lib/protip.rb
206
196
  - lib/protip/client.rb
207
197
  - lib/protip/converter.rb
208
198
  - lib/protip/error.rb
199
+ - lib/protip/extensions.rb
209
200
  - lib/protip/messages/active_support/time_with_zone.rb
210
201
  - lib/protip/messages/array.rb
211
202
  - lib/protip/messages/currency.rb
@@ -213,7 +204,9 @@ files:
213
204
  - lib/protip/messages/money.rb
214
205
  - lib/protip/messages/range.rb
215
206
  - lib/protip/messages/repeated_wrappers.rb
207
+ - lib/protip/messages/test.rb
216
208
  - lib/protip/messages/types.rb
209
+ - lib/protip/messages/wrappers.rb
217
210
  - lib/protip/resource.rb
218
211
  - lib/protip/resource/associations/association.rb
219
212
  - lib/protip/resource/associations/belongs_to_association.rb