protobuf 3.6.12 → 3.7.0.pre0

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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +4 -4
  3. data/CHANGES.md +14 -3
  4. data/lib/protobuf/descriptors/google/protobuf/descriptor.pb.rb +39 -2
  5. data/lib/protobuf/field.rb +2 -2
  6. data/lib/protobuf/field/base_field.rb +27 -84
  7. data/lib/protobuf/field/bool_field.rb +3 -13
  8. data/lib/protobuf/field/bytes_field.rb +10 -26
  9. data/lib/protobuf/field/enum_field.rb +10 -20
  10. data/lib/protobuf/field/message_field.rb +13 -23
  11. data/lib/protobuf/generators/enum_generator.rb +1 -0
  12. data/lib/protobuf/generators/field_generator.rb +8 -2
  13. data/lib/protobuf/generators/file_generator.rb +8 -0
  14. data/lib/protobuf/generators/service_generator.rb +2 -2
  15. data/lib/protobuf/message.rb +47 -12
  16. data/lib/protobuf/message/fields.rb +80 -8
  17. data/lib/protobuf/rpc/connectors/common.rb +1 -1
  18. data/lib/protobuf/rpc/connectors/ping.rb +2 -2
  19. data/lib/protobuf/rpc/connectors/zmq.rb +1 -1
  20. data/lib/protobuf/rpc/middleware/exception_handler.rb +0 -4
  21. data/lib/protobuf/rpc/middleware/logger.rb +0 -4
  22. data/lib/protobuf/rpc/middleware/request_decoder.rb +16 -11
  23. data/lib/protobuf/rpc/middleware/response_encoder.rb +20 -15
  24. data/lib/protobuf/rpc/service.rb +10 -2
  25. data/lib/protobuf/rpc/service_directory.rb +0 -8
  26. data/lib/protobuf/rpc/service_dispatcher.rb +6 -5
  27. data/lib/protobuf/rpc/service_filters.rb +30 -8
  28. data/lib/protobuf/version.rb +1 -1
  29. data/proto/google/protobuf/descriptor.proto +190 -31
  30. data/spec/lib/protobuf/field/bool_field_spec.rb +33 -7
  31. data/spec/lib/protobuf/field/enum_field_spec.rb +26 -0
  32. data/spec/lib/protobuf/field/float_field_spec.rb +32 -1
  33. data/spec/lib/protobuf/field/int32_field_spec.rb +32 -1
  34. data/spec/lib/protobuf/field/message_field_spec.rb +79 -0
  35. data/spec/lib/protobuf/field/string_field_spec.rb +34 -0
  36. data/spec/lib/protobuf/field_spec.rb +1 -0
  37. data/spec/lib/protobuf/generators/enum_generator_spec.rb +9 -0
  38. data/spec/lib/protobuf/generators/service_generator_spec.rb +9 -0
  39. data/spec/lib/protobuf/message_spec.rb +328 -63
  40. data/spec/lib/protobuf/rpc/connectors/ping_spec.rb +3 -3
  41. data/spec/lib/protobuf/rpc/service_dispatcher_spec.rb +18 -1
  42. data/spec/support/protos/enum.pb.rb +1 -1
  43. data/spec/support/protos/google_unittest.pb.rb +113 -111
  44. data/spec/support/protos/google_unittest.proto +7 -0
  45. data/spec/support/protos/multi_field_extensions.pb.rb +1 -1
  46. data/spec/support/protos/resource.pb.rb +9 -9
  47. metadata +79 -5
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- RSpec.describe Protobuf::Field::Int32Field do
3
+ RSpec.describe Protobuf::Field::BoolField do
4
4
 
5
5
  class SomeBoolMessage < ::Protobuf::Message
6
6
  optional :bool, :some_bool, 1
@@ -9,7 +9,7 @@ RSpec.describe Protobuf::Field::Int32Field do
9
9
 
10
10
  let(:instance) { SomeBoolMessage.new }
11
11
 
12
- describe '#define_setter' do
12
+ describe 'setting and getting field' do
13
13
  subject { instance.some_bool = value; instance.some_bool }
14
14
 
15
15
  [true, false].each do |val|
@@ -49,12 +49,38 @@ RSpec.describe Protobuf::Field::Int32Field do
49
49
  end
50
50
  end
51
51
 
52
- describe '#define_getter' do
53
- context 'when required bool field is set to false' do
54
- subject { instance.required_bool = false; instance.required_bool }
52
+ it 'defines ? method' do
53
+ instance.required_bool = false
54
+ expect(instance.required_bool?).to be(false)
55
+ end
56
+
57
+ describe '#default_value' do
58
+ context 'optional and required fields' do
59
+ it 'returns the class default' do
60
+ expect(SomeBoolMessage.get_field('some_bool').default).to be nil
61
+ expect(::Protobuf::Field::BoolField.default).to be false
62
+ expect(instance.some_bool).to be false
63
+ end
64
+
65
+ context 'with field default' do
66
+ class AnotherBoolMessage < ::Protobuf::Message
67
+ optional :bool, :set_bool, 1, :default => true
68
+ end
69
+
70
+ it 'returns the set default' do
71
+ expect(AnotherBoolMessage.get_field('set_bool').default).to be true
72
+ expect(AnotherBoolMessage.new.set_bool).to be true
73
+ end
74
+ end
75
+ end
76
+
77
+ context 'repeated field' do
78
+ class RepeatedBoolMessage < ::Protobuf::Message
79
+ repeated :bool, :repeated_bool, 1
80
+ end
55
81
 
56
- it 'returns false' do
57
- expect(subject).to eq(false)
82
+ it 'returns the set default' do
83
+ expect(RepeatedBoolMessage.new.repeated_bool).to eq []
58
84
  end
59
85
  end
60
86
  end
@@ -0,0 +1,26 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Protobuf::Field::EnumField do
4
+ let(:message) do
5
+ Class.new(::Protobuf::Message) do
6
+ enum_class = Class.new(::Protobuf::Enum) do
7
+ define :POSITIVE, 22
8
+ define :NEGATIVE, -33
9
+ end
10
+
11
+ optional enum_class, :enum, 1
12
+ end
13
+ end
14
+
15
+ describe '.{encode, decode}' do
16
+ it 'handles positive enum constants' do
17
+ instance = message.new(:enum => :POSITIVE)
18
+ expect(message.decode(instance.encode).enum).to eq(22)
19
+ end
20
+
21
+ it 'handles negative enum constants' do
22
+ instance = message.new(:enum => :NEGATIVE)
23
+ expect(message.decode(instance.encode).enum).to eq(-33)
24
+ end
25
+ end
26
+ end
@@ -8,7 +8,7 @@ RSpec.describe Protobuf::Field::FloatField do
8
8
 
9
9
  let(:instance) { SomeFloatMessage.new }
10
10
 
11
- describe '#define_setter' do
11
+ describe 'setting and getting field' do
12
12
  subject { instance.some_float = value; instance.some_float }
13
13
 
14
14
  context 'when set with an int' do
@@ -52,4 +52,35 @@ RSpec.describe Protobuf::Field::FloatField do
52
52
  end
53
53
  end
54
54
 
55
+ describe '#default_value' do
56
+ context 'optional and required fields' do
57
+ it 'returns the class default' do
58
+ expect(SomeFloatMessage.get_field('some_float').default).to be nil
59
+ expect(::Protobuf::Field::FloatField.default).to eq 0.0
60
+ expect(instance.some_float).to eq 0.0
61
+ end
62
+
63
+ context 'with field default' do
64
+ class AnotherFloatMessage < ::Protobuf::Message
65
+ optional :float, :set_float, 1, :default => 3.6
66
+ end
67
+
68
+ it 'returns the set default' do
69
+ expect(AnotherFloatMessage.get_field('set_float').default).to eq 3.6
70
+ expect(AnotherFloatMessage.new.set_float).to eq 3.6
71
+ end
72
+ end
73
+ end
74
+
75
+ context 'repeated field' do
76
+ class RepeatedFloatMessage < ::Protobuf::Message
77
+ repeated :float, :repeated_float, 1
78
+ end
79
+
80
+ it 'returns the set default' do
81
+ expect(RepeatedFloatMessage.new.repeated_float).to eq []
82
+ end
83
+ end
84
+ end
85
+
55
86
  end
@@ -10,7 +10,7 @@ RSpec.describe Protobuf::Field::Int32Field do
10
10
 
11
11
  let(:instance) { SomeInt32Message.new }
12
12
 
13
- describe '#define_setter' do
13
+ describe 'setting and getting a field' do
14
14
  subject { instance.some_int = value; instance.some_int }
15
15
 
16
16
  context 'when set with an int' do
@@ -86,4 +86,35 @@ RSpec.describe Protobuf::Field::Int32Field do
86
86
  end
87
87
  end
88
88
 
89
+ describe '#default_value' do
90
+ context 'optional and required fields' do
91
+ it 'returns the class default' do
92
+ expect(SomeInt32Message.get_field('some_int').default).to be nil
93
+ expect(::Protobuf::Field::Int32Field.default).to eq 0
94
+ expect(instance.some_int).to eq 0
95
+ end
96
+
97
+ context 'with field default' do
98
+ class AnotherIntMessage < ::Protobuf::Message
99
+ optional :int32, :set_int, 1, :default => 3
100
+ end
101
+
102
+ it 'returns the set default' do
103
+ expect(AnotherIntMessage.get_field('set_int').default).to eq 3
104
+ expect(AnotherIntMessage.new.set_int).to eq 3
105
+ end
106
+ end
107
+ end
108
+
109
+ context 'repeated field' do
110
+ class RepeatedIntMessage < ::Protobuf::Message
111
+ repeated :int32, :repeated_int, 1
112
+ end
113
+
114
+ it 'returns the set default' do
115
+ expect(RepeatedIntMessage.new.repeated_int).to eq []
116
+ end
117
+ end
118
+ end
119
+
89
120
  end
@@ -0,0 +1,79 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Protobuf::Field::MessageField do
4
+ let(:field_message) do
5
+ Class.new(::Protobuf::Message) do
6
+ optional :int32, :field, 1
7
+ end
8
+ end
9
+
10
+ let(:message) do
11
+ Class.new(::Protobuf::Message) do
12
+ optional FieldMessage, :message_field, 1
13
+ end
14
+ end
15
+
16
+ before do
17
+ stub_const('FieldMessage', field_message)
18
+ stub_const('Message', message)
19
+ end
20
+
21
+ let(:instance) { message.new }
22
+
23
+ describe 'setting and getting field' do
24
+ context "when set with the message type" do
25
+ it 'is readable as a message' do
26
+ value = field_message.new(:field => 34)
27
+ instance.message_field = value
28
+ expect(instance.message_field).to eq(value)
29
+ end
30
+ end
31
+
32
+ context "when set with #to_proto" do
33
+ let(:to_proto_message) do
34
+ Class.new do
35
+ def to_proto
36
+ FieldMessage.new(:field => 42)
37
+ end
38
+ end
39
+ end
40
+
41
+ it 'is readable as a message' do
42
+ value = to_proto_message.new
43
+ instance.message_field = value
44
+ expect(instance.message_field).to eq(value.to_proto)
45
+ end
46
+ end
47
+
48
+ context "when set with #to_proto that returns the wrong message type" do
49
+ let(:to_proto_is_wrong_message) do
50
+ Class.new do
51
+ def to_proto
52
+ Message.new
53
+ end
54
+ end
55
+ end
56
+
57
+ it 'fails' do
58
+ value = to_proto_is_wrong_message.new
59
+ expect { instance.message_field = value }.to raise_error TypeError
60
+ end
61
+ end
62
+
63
+ context "when set with #to_hash" do
64
+ let(:to_hash_message) do
65
+ Class.new do
66
+ def to_hash
67
+ { :field => 989 }
68
+ end
69
+ end
70
+ end
71
+
72
+ it 'is readable as a message' do
73
+ value = to_hash_message.new
74
+ instance.message_field = value
75
+ expect(instance.message_field).to eq(field_message.new(value.to_hash))
76
+ end
77
+ end
78
+ end
79
+ end
@@ -42,4 +42,38 @@ RSpec.describe ::Protobuf::Field::StringField do
42
42
  end
43
43
  end
44
44
 
45
+ describe '#default_value' do
46
+ context 'optional and required fields' do
47
+ it 'returns the class default' do
48
+ class SomeStringMessage < ::Protobuf::Message
49
+ optional :string, :some_string, 1
50
+ end
51
+ expect(SomeStringMessage.get_field('some_string').default).to be nil
52
+ expect(::Protobuf::Field::StringField.default).to eq ""
53
+ expect(SomeStringMessage.new.some_string).to eq ""
54
+ end
55
+
56
+ context 'with field default' do
57
+ class AnotherStringMessage < ::Protobuf::Message
58
+ optional :string, :set_string, 1, :default => "default value this is"
59
+ end
60
+
61
+ it 'returns the set default' do
62
+ expect(AnotherStringMessage.get_field('set_string').default).to eq "default value this is"
63
+ expect(AnotherStringMessage.new.set_string).to eq "default value this is"
64
+ end
65
+ end
66
+ end
67
+
68
+ context 'repeated field' do
69
+ class RepeatedStringMessage < ::Protobuf::Message
70
+ repeated :string, :repeated_string, 1
71
+ end
72
+
73
+ it 'returns the set default' do
74
+ expect(RepeatedStringMessage.new.repeated_string).to eq []
75
+ end
76
+ end
77
+ end
78
+
45
79
  end
@@ -1,5 +1,6 @@
1
1
  require 'spec_helper'
2
2
  require 'protobuf/field'
3
+ require PROTOS_PATH.join('resource.pb')
3
4
 
4
5
  RSpec.describe ::Protobuf::Field do
5
6
 
@@ -68,6 +68,15 @@ end
68
68
  it 'returns a string identifying the given enum value' do
69
69
  expect(subject.build_value(enum.value.first)).to eq("define :FOO, 1")
70
70
  end
71
+
72
+ context 'with PB_UPCASE_ENUMS set' do
73
+ before { allow(ENV).to receive(:key?).with('PB_UPCASE_ENUMS').and_return(true) }
74
+ let(:values) { [{ :name => 'boom', :number => 1 }] }
75
+
76
+ it 'returns a string with the given enum name in ALL CAPS' do
77
+ expect(subject.build_value(enum.value.first)).to eq("define :BOOM, 1")
78
+ end
79
+ end
71
80
  end
72
81
 
73
82
  end
@@ -41,6 +41,15 @@ end
41
41
  it 'returns a string identifying the given method descriptor' do
42
42
  expect(subject.build_method(service.method.first)).to eq("rpc :search, FooRequest, FooResponse")
43
43
  end
44
+
45
+ context 'with PB_USE_RAW_RPC_NAMES in the environemnt' do
46
+ before { allow(ENV).to receive(:key?).with('PB_USE_RAW_RPC_NAMES').and_return(true) }
47
+
48
+ it 'uses the raw RPC name and does not underscore it' do
49
+ expect(subject.build_method(service.method.first)).to eq("rpc :Search, FooRequest, FooResponse")
50
+ expect(subject.build_method(service.method.last)).to eq("rpc :FooBar, ::Foo::Request, ::Bar::Response")
51
+ end
52
+ end
44
53
  end
45
54
 
46
55
  end
@@ -181,64 +181,6 @@ RSpec.describe Protobuf::Message do
181
181
  test_enum = Test::EnumTestMessage.new { |p| p.non_default_enum = 2 }
182
182
  expect(test_enum.non_default_enum).to eq(2)
183
183
  end
184
-
185
- context 'ignoring unknown fields' do
186
- around do |example|
187
- orig = ::Protobuf.ignore_unknown_fields?
188
- ::Protobuf.ignore_unknown_fields = true
189
- example.call
190
- ::Protobuf.ignore_unknown_fields = orig
191
- end
192
-
193
- context 'with valid fields' do
194
- let(:values) { { :name => "Jim" } }
195
-
196
- it "does not raise an error" do
197
- expect { ::Test::Resource.new(values) }.to_not raise_error
198
- end
199
- end
200
-
201
- context 'with non-existent field' do
202
- let(:values) { { :name => "Jim", :othername => "invalid" } }
203
-
204
- it "does not raise an error" do
205
- expect { ::Test::Resource.new(values) }.to_not raise_error
206
- end
207
- end
208
- end
209
-
210
- context 'not ignoring unknown fields' do
211
- around do |example|
212
- orig = ::Protobuf.ignore_unknown_fields?
213
- ::Protobuf.ignore_unknown_fields = false
214
- example.call
215
- ::Protobuf.ignore_unknown_fields = orig
216
- end
217
-
218
- context 'with valid fields' do
219
- let(:values) { { :name => "Jim" } }
220
-
221
- it "does not raise an error" do
222
- expect { ::Test::Resource.new(values) }.to_not raise_error
223
- end
224
- end
225
-
226
- context 'with non-existent field' do
227
- let(:values) { { :name => "Jim", :othername => "invalid" } }
228
-
229
- it "raises an error and mentions the erroneous field" do
230
- expect { ::Test::Resource.new(values) }.to raise_error(::Protobuf::FieldNotDefinedError, /othername/)
231
- end
232
-
233
- context 'with a nil value' do
234
- let(:values) { { :name => "Jim", :othername => nil } }
235
-
236
- it "raises an error and mentions the erroneous field" do
237
- expect { ::Test::Resource.new(values) }.to raise_error(::Protobuf::FieldNotDefinedError, /othername/)
238
- end
239
- end
240
- end
241
- end
242
184
  end
243
185
 
244
186
  describe '#encode' do
@@ -448,6 +390,29 @@ RSpec.describe Protobuf::Message do
448
390
  )
449
391
  end
450
392
  end
393
+
394
+ it 'uses simple field names as keys when possible and fully qualified names otherwise' do
395
+ message = Class.new(::Protobuf::Message) do
396
+ optional :int32, :field, 1
397
+ optional :int32, :colliding_field, 2
398
+ extensions 100...200
399
+ optional :int32, :".ext.normal_ext_field", 100, :extension => true
400
+ optional :int32, :".ext.colliding_field", 101, :extension => true
401
+ optional :int32, :".ext.colliding_field2", 102, :extension => true
402
+ optional :int32, :".ext2.colliding_field2", 103, :extension => true
403
+ end
404
+
405
+ hash = {
406
+ :field => 1,
407
+ :colliding_field => 2,
408
+ :normal_ext_field => 3,
409
+ :".ext.colliding_field" => 4,
410
+ :".ext.colliding_field2" => 5,
411
+ :".ext2.colliding_field2" => 6,
412
+ }
413
+ instance = message.new(hash)
414
+ expect(instance.to_hash).to eq(hash)
415
+ end
451
416
  end
452
417
 
453
418
  describe '#to_json' do
@@ -468,16 +433,40 @@ RSpec.describe Protobuf::Message do
468
433
  end
469
434
  end
470
435
 
471
- describe "#define_setter" do
436
+ describe "#define_accessor" do
472
437
  subject { ::Test::Resource.new }
473
438
 
474
- it "allows string fields to be set to nil" do
439
+ it 'allows string fields to be set to nil' do
475
440
  expect { subject.name = nil }.to_not raise_error
476
441
  end
477
442
 
478
- it "does not allow string fields to be set to Numeric" do
443
+ it 'does not allow string fields to be set to Numeric' do
479
444
  expect { subject.name = 1 }.to raise_error(/name/)
480
445
  end
446
+
447
+ context '#{simple_field_name}!' do
448
+ it 'returns value of set field' do
449
+ expect(::Test::Resource.new(:name => "Joe").name!).to eq("Joe")
450
+ end
451
+
452
+ it 'returns value of set field with default' do
453
+ expect(::Test::Resource.new(:name => "").name!).to eq("")
454
+ end
455
+
456
+ it 'returns nil if extension field is unset' do
457
+ expect(subject.ext_is_searchable!).to be_nil
458
+ end
459
+
460
+ it 'returns value of set extension field' do
461
+ message = ::Test::Resource.new(:ext_is_searchable => true)
462
+ expect(message.ext_is_searchable!).to be(true)
463
+ end
464
+
465
+ it 'returns value of set extension field with default' do
466
+ message = ::Test::Resource.new(:ext_is_searchable => false)
467
+ expect(message.ext_is_searchable!).to be(false)
468
+ end
469
+ end
481
470
  end
482
471
 
483
472
  describe '.get_extension_field' do
@@ -486,12 +475,15 @@ RSpec.describe Protobuf::Message do
486
475
  expect(field).to be_a(::Protobuf::Field::BoolField)
487
476
  expect(field.tag).to eq(100)
488
477
  expect(field.name).to eq(:ext_is_searchable)
478
+ expect(field.fully_qualified_name).to eq(:'.test.Searchable.ext_is_searchable')
489
479
  expect(field).to be_extension
490
480
  end
491
481
 
492
482
  it 'fetches an extension field by its symbolized name' do
493
483
  expect(::Test::Resource.get_extension_field(:ext_is_searchable)).to be_a(::Protobuf::Field::BoolField)
494
484
  expect(::Test::Resource.get_extension_field('ext_is_searchable')).to be_a(::Protobuf::Field::BoolField)
485
+ expect(::Test::Resource.get_extension_field(:'.test.Searchable.ext_is_searchable')).to be_a(::Protobuf::Field::BoolField)
486
+ expect(::Test::Resource.get_extension_field('.test.Searchable.ext_is_searchable')).to be_a(::Protobuf::Field::BoolField)
495
487
  end
496
488
 
497
489
  it 'returns nil when attempting to get a non-extension field' do
@@ -504,12 +496,71 @@ RSpec.describe Protobuf::Message do
504
496
  end
505
497
  end
506
498
 
499
+ describe '#field?' do
500
+ it 'returns false for non-existent field' do
501
+ expect(::Test::Resource.get_field('doesnotexist')).to be_nil
502
+ expect(::Test::Resource.new.field?('doesnotexist')).to be(false)
503
+ end
504
+
505
+ it 'returns false for unset field' do
506
+ expect(::Test::Resource.get_field('name')).to be
507
+ expect(::Test::Resource.new.field?('name')).to be(false)
508
+ end
509
+
510
+ it 'returns false for unset field from tag' do
511
+ expect(::Test::Resource.get_field(1)).to be
512
+ expect(::Test::Resource.new.field?(1)).to be(false)
513
+ end
514
+
515
+ it 'returns true for set field' do
516
+ expect(::Test::Resource.new(:name => "Joe").field?('name')).to be(true)
517
+ end
518
+
519
+ it 'returns true for set field with default' do
520
+ expect(::Test::Resource.new(:name => "").field?('name')).to be(true)
521
+ end
522
+
523
+ it 'returns true from field tag value' do
524
+ expect(::Test::Resource.new(:name => "Joe").field?(1)).to be(true)
525
+ end
526
+
527
+ it 'returns false for unset extension field' do
528
+ ext_field = :".test.Searchable.ext_is_searchable"
529
+ expect(::Test::Resource.get_extension_field(ext_field)).to be
530
+ expect(::Test::Resource.new.field?(ext_field)).to be(false)
531
+ end
532
+
533
+ it 'returns false for unset extension field from tag' do
534
+ expect(::Test::Resource.get_extension_field(100)).to be
535
+ expect(::Test::Resource.new.field?(100)).to be(false)
536
+ end
537
+
538
+ it 'returns true for set extension field' do
539
+ ext_field = :".test.Searchable.ext_is_searchable"
540
+ message = ::Test::Resource.new(ext_field => true)
541
+ expect(message.field?(ext_field)).to be(true)
542
+ end
543
+
544
+ it 'returns true for set extension field with default' do
545
+ ext_field = :".test.Searchable.ext_is_searchable"
546
+ message = ::Test::Resource.new(ext_field => false)
547
+ expect(message.field?(ext_field)).to be(true)
548
+ end
549
+
550
+ it 'returns true for set extension field from tag' do
551
+ ext_field = :".test.Searchable.ext_is_searchable"
552
+ message = ::Test::Resource.new(ext_field => false)
553
+ expect(message.field?(100)).to be(true)
554
+ end
555
+ end
556
+
507
557
  describe '.get_field' do
508
558
  it 'fetches a non-extension field by its tag' do
509
559
  field = ::Test::Resource.get_field(1)
510
560
  expect(field).to be_a(::Protobuf::Field::StringField)
511
561
  expect(field.tag).to eq(1)
512
562
  expect(field.name).to eq(:name)
563
+ expect(field.fully_qualified_name).to eq(:name)
513
564
  expect(field).not_to be_extension
514
565
  end
515
566
 
@@ -520,8 +571,8 @@ RSpec.describe Protobuf::Message do
520
571
 
521
572
  it 'fetches an extension field when forced' do
522
573
  expect(::Test::Resource.get_field(100, true)).to be_a(::Protobuf::Field::BoolField)
523
- expect(::Test::Resource.get_field(:ext_is_searchable, true)).to be_a(::Protobuf::Field::BoolField)
524
- expect(::Test::Resource.get_field('ext_is_searchable', true)).to be_a(::Protobuf::Field::BoolField)
574
+ expect(::Test::Resource.get_field(:'.test.Searchable.ext_is_searchable', true)).to be_a(::Protobuf::Field::BoolField)
575
+ expect(::Test::Resource.get_field('.test.Searchable.ext_is_searchable', true)).to be_a(::Protobuf::Field::BoolField)
525
576
  end
526
577
 
527
578
  it 'returns nil when attempting to get an extension field' do
@@ -534,4 +585,218 @@ RSpec.describe Protobuf::Message do
534
585
  end
535
586
  end
536
587
 
588
+ describe 'defining a field' do
589
+ # Case 1
590
+ context 'single base field' do
591
+ let(:klass) do
592
+ Class.new(Protobuf::Message) do
593
+ optional :string, :foo, 1
594
+ end
595
+ end
596
+
597
+ it 'has an accessor for foo' do
598
+ message = klass.new(:foo => 'bar')
599
+ expect(message.foo).to eq('bar')
600
+ expect(message[:foo]).to eq('bar')
601
+ expect(message['foo']).to eq('bar')
602
+ end
603
+ end
604
+
605
+ # Case 2
606
+ context 'base field and extension field name collision' do
607
+ let(:klass) do
608
+ Class.new(Protobuf::Message) do
609
+ optional :string, :foo, 1
610
+ optional :string, :".boom.foo", 2, :extension => true
611
+ end
612
+ end
613
+
614
+ it 'has an accessor for foo that refers to the base field' do
615
+ message = klass.new(:foo => 'bar', '.boom.foo' => 'bam')
616
+ expect(message.foo).to eq('bar')
617
+ expect(message[:foo]).to eq('bar')
618
+ expect(message['foo']).to eq('bar')
619
+ expect(message[:'.boom.foo']).to eq('bam')
620
+ expect(message['.boom.foo']).to eq('bam')
621
+ end
622
+ end
623
+
624
+ # Case 3
625
+ context 'no base field with extension fields with name collision' do
626
+ let(:klass) do
627
+ Class.new(Protobuf::Message) do
628
+ optional :string, :".boom.foo", 2, :extension => true
629
+ optional :string, :".goat.foo", 3, :extension => true
630
+ end
631
+ end
632
+
633
+ it 'has an accessor for foo that refers to the extension field' do
634
+ message = klass.new('.boom.foo' => 'bam', '.goat.foo' => 'red')
635
+ expect { message.foo }.to raise_error(NoMethodError)
636
+ expect { message[:foo] }.to raise_error(ArgumentError)
637
+ expect { message['foo'] }.to raise_error(ArgumentError)
638
+ expect(message[:'.boom.foo']).to eq('bam')
639
+ expect(message['.boom.foo']).to eq('bam')
640
+ expect(message[:'.goat.foo']).to eq('red')
641
+ expect(message['.goat.foo']).to eq('red')
642
+ end
643
+ end
644
+
645
+ # Case 4
646
+ context 'no base field with an extension field' do
647
+ let(:klass) do
648
+ Class.new(Protobuf::Message) do
649
+ optional :string, :".boom.foo", 2, :extension => true
650
+ end
651
+ end
652
+
653
+ it 'has an accessor for foo that refers to the extension field' do
654
+ message = klass.new('.boom.foo' => 'bam')
655
+ expect(message.foo).to eq('bam')
656
+ expect(message[:foo]).to eq('bam')
657
+ expect(message['foo']).to eq('bam')
658
+ expect(message[:'.boom.foo']).to eq('bam')
659
+ expect(message['.boom.foo']).to eq('bam')
660
+ end
661
+ end
662
+ end
663
+
664
+ describe '.[]=' do
665
+ context 'clearing fields' do
666
+ it 'clears repeated fields with an empty array' do
667
+ instance = ::Test::Resource.new(:repeated_enum => [::Test::StatusType::ENABLED])
668
+ expect(instance.field?(:repeated_enum)).to be(true)
669
+ instance[:repeated_enum] = []
670
+ expect(instance.field?(:repeated_enum)).to be(false)
671
+ end
672
+
673
+ it 'clears optional fields with nil' do
674
+ instance = ::Test::Resource.new(:name => "Joe")
675
+ expect(instance.field?(:name)).to be(true)
676
+ instance[:name] = nil
677
+ expect(instance.field?(:name)).to be(false)
678
+ end
679
+
680
+ it 'clears optional extenstion fields with nil' do
681
+ instance = ::Test::Resource.new(:ext_is_searchable => true)
682
+ expect(instance.field?(:ext_is_searchable)).to be(true)
683
+ instance[:ext_is_searchable] = nil
684
+ expect(instance.field?(:ext_is_searchable)).to be(false)
685
+ end
686
+ end
687
+
688
+ context 'setting fields' do
689
+ let(:instance) { ::Test::Resource.new }
690
+
691
+ it 'sets and replaces repeated fields' do
692
+ initial = [::Test::StatusType::ENABLED, ::Test::StatusType::DISABLED]
693
+ instance[:repeated_enum] = initial
694
+ expect(instance[:repeated_enum]).to eq(initial)
695
+ replacement = [::Test::StatusType::DELETED]
696
+ instance[:repeated_enum] = replacement
697
+ expect(instance[:repeated_enum]).to eq(replacement)
698
+ end
699
+
700
+ it 'sets acceptable optional field values' do
701
+ instance[:name] = "Joe"
702
+ expect(instance[:name]).to eq("Joe")
703
+ instance[1] = "Tom"
704
+ expect(instance[:name]).to eq("Tom")
705
+ end
706
+
707
+ it 'sets acceptable empty string field values' do
708
+ instance[:name] = ""
709
+ expect(instance.name!).to eq("")
710
+ end
711
+
712
+ it 'sets acceptable empty message field values' do
713
+ instance = ::Test::Nested.new
714
+ instance[:resource] = {}
715
+ expect(instance.resource!).to eq(::Test::Resource.new)
716
+ end
717
+
718
+ it 'sets acceptable extension field values' do
719
+ instance[:ext_is_searchable] = true
720
+ expect(instance[:ext_is_searchable]).to eq(true)
721
+ instance[:".test.Searchable.ext_is_searchable"] = false
722
+ expect(instance[:ext_is_searchable]).to eq(false)
723
+ instance[100] = true
724
+ expect(instance[:ext_is_searchable]).to eq(true)
725
+ end
726
+ end
727
+
728
+ context 'throwing TypeError' do
729
+ let(:instance) { ::Test::Resource.new }
730
+
731
+ it 'throws when a repeated value is set with a non array' do
732
+ expect { instance[:repeated_enum] = "string" }.to raise_error(TypeError)
733
+ end
734
+
735
+ it 'throws when a repeated value is set with an array of the wrong type' do
736
+ expect { instance[:repeated_enum] = [true, false] }.to raise_error(TypeError)
737
+ end
738
+
739
+ it 'throws when an optional value is not #acceptable?' do
740
+ expect { instance[:name] = 1 }.to raise_error(TypeError)
741
+ end
742
+ end
743
+
744
+ context 'ignoring unknown fields' do
745
+ around do |example|
746
+ orig = ::Protobuf.ignore_unknown_fields?
747
+ ::Protobuf.ignore_unknown_fields = true
748
+ example.call
749
+ ::Protobuf.ignore_unknown_fields = orig
750
+ end
751
+
752
+ context 'with valid fields' do
753
+ let(:values) { { :name => "Jim" } }
754
+
755
+ it "does not raise an error" do
756
+ expect { ::Test::Resource.new(values) }.to_not raise_error
757
+ end
758
+ end
759
+
760
+ context 'with non-existent field' do
761
+ let(:values) { { :name => "Jim", :othername => "invalid" } }
762
+
763
+ it "does not raise an error" do
764
+ expect { ::Test::Resource.new(values) }.to_not raise_error
765
+ end
766
+ end
767
+ end
768
+
769
+ context 'not ignoring unknown fields' do
770
+ around do |example|
771
+ orig = ::Protobuf.ignore_unknown_fields?
772
+ ::Protobuf.ignore_unknown_fields = false
773
+ example.call
774
+ ::Protobuf.ignore_unknown_fields = orig
775
+ end
776
+
777
+ context 'with valid fields' do
778
+ let(:values) { { :name => "Jim" } }
779
+
780
+ it "does not raise an error" do
781
+ expect { ::Test::Resource.new(values) }.to_not raise_error
782
+ end
783
+ end
784
+
785
+ context 'with non-existent field' do
786
+ let(:values) { { :name => "Jim", :othername => "invalid" } }
787
+
788
+ it "raises an error and mentions the erroneous field" do
789
+ expect { ::Test::Resource.new(values) }.to raise_error(::Protobuf::FieldNotDefinedError, /othername/)
790
+ end
791
+
792
+ context 'with a nil value' do
793
+ let(:values) { { :name => "Jim", :othername => nil } }
794
+
795
+ it "raises an error and mentions the erroneous field" do
796
+ expect { ::Test::Resource.new(values) }.to raise_error(::Protobuf::FieldNotDefinedError, /othername/)
797
+ end
798
+ end
799
+ end
800
+ end
801
+ end
537
802
  end