grape 1.1.0 → 1.2.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (111) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +128 -43
  3. data/LICENSE +1 -1
  4. data/README.md +394 -47
  5. data/UPGRADING.md +111 -0
  6. data/grape.gemspec +3 -1
  7. data/lib/grape.rb +98 -66
  8. data/lib/grape/api.rb +136 -175
  9. data/lib/grape/api/instance.rb +280 -0
  10. data/lib/grape/config.rb +32 -0
  11. data/lib/grape/dsl/callbacks.rb +20 -0
  12. data/lib/grape/dsl/desc.rb +39 -7
  13. data/lib/grape/dsl/inside_route.rb +12 -6
  14. data/lib/grape/dsl/middleware.rb +7 -0
  15. data/lib/grape/dsl/parameters.rb +9 -4
  16. data/lib/grape/dsl/routing.rb +5 -1
  17. data/lib/grape/dsl/validations.rb +4 -3
  18. data/lib/grape/eager_load.rb +18 -0
  19. data/lib/grape/endpoint.rb +42 -26
  20. data/lib/grape/error_formatter.rb +1 -1
  21. data/lib/grape/exceptions/base.rb +9 -1
  22. data/lib/grape/exceptions/invalid_response.rb +9 -0
  23. data/lib/grape/exceptions/validation_errors.rb +4 -2
  24. data/lib/grape/formatter.rb +1 -1
  25. data/lib/grape/locale/en.yml +2 -0
  26. data/lib/grape/middleware/auth/base.rb +2 -4
  27. data/lib/grape/middleware/base.rb +2 -0
  28. data/lib/grape/middleware/error.rb +9 -4
  29. data/lib/grape/middleware/helpers.rb +10 -0
  30. data/lib/grape/middleware/stack.rb +1 -1
  31. data/lib/grape/middleware/versioner/header.rb +4 -4
  32. data/lib/grape/parser.rb +1 -1
  33. data/lib/grape/request.rb +1 -1
  34. data/lib/grape/router/attribute_translator.rb +2 -0
  35. data/lib/grape/router/route.rb +2 -2
  36. data/lib/grape/util/base_inheritable.rb +34 -0
  37. data/lib/grape/util/endpoint_configuration.rb +6 -0
  38. data/lib/grape/util/inheritable_values.rb +5 -25
  39. data/lib/grape/util/lazy_block.rb +25 -0
  40. data/lib/grape/util/lazy_value.rb +95 -0
  41. data/lib/grape/util/reverse_stackable_values.rb +7 -36
  42. data/lib/grape/util/stackable_values.rb +19 -22
  43. data/lib/grape/validations/attributes_iterator.rb +5 -3
  44. data/lib/grape/validations/multiple_attributes_iterator.rb +11 -0
  45. data/lib/grape/validations/params_scope.rb +20 -14
  46. data/lib/grape/validations/single_attribute_iterator.rb +13 -0
  47. data/lib/grape/validations/types/custom_type_coercer.rb +1 -1
  48. data/lib/grape/validations/types/file.rb +1 -1
  49. data/lib/grape/validations/validator_factory.rb +6 -11
  50. data/lib/grape/validations/validators/all_or_none.rb +6 -13
  51. data/lib/grape/validations/validators/as.rb +2 -3
  52. data/lib/grape/validations/validators/at_least_one_of.rb +5 -13
  53. data/lib/grape/validations/validators/base.rb +11 -10
  54. data/lib/grape/validations/validators/coerce.rb +4 -0
  55. data/lib/grape/validations/validators/default.rb +1 -1
  56. data/lib/grape/validations/validators/exactly_one_of.rb +6 -23
  57. data/lib/grape/validations/validators/multiple_params_base.rb +14 -10
  58. data/lib/grape/validations/validators/mutual_exclusion.rb +6 -18
  59. data/lib/grape/validations/validators/same_as.rb +23 -0
  60. data/lib/grape/version.rb +1 -1
  61. data/spec/grape/api/defines_boolean_in_params_spec.rb +37 -0
  62. data/spec/grape/api/routes_with_requirements_spec.rb +59 -0
  63. data/spec/grape/api_remount_spec.rb +466 -0
  64. data/spec/grape/api_spec.rb +379 -1
  65. data/spec/grape/config_spec.rb +17 -0
  66. data/spec/grape/dsl/desc_spec.rb +40 -16
  67. data/spec/grape/dsl/middleware_spec.rb +8 -0
  68. data/spec/grape/dsl/routing_spec.rb +10 -0
  69. data/spec/grape/endpoint_spec.rb +40 -4
  70. data/spec/grape/exceptions/base_spec.rb +65 -0
  71. data/spec/grape/exceptions/invalid_response_spec.rb +11 -0
  72. data/spec/grape/exceptions/validation_errors_spec.rb +6 -4
  73. data/spec/grape/integration/rack_spec.rb +22 -6
  74. data/spec/grape/middleware/auth/dsl_spec.rb +3 -3
  75. data/spec/grape/middleware/base_spec.rb +8 -0
  76. data/spec/grape/middleware/exception_spec.rb +1 -1
  77. data/spec/grape/middleware/formatter_spec.rb +15 -5
  78. data/spec/grape/middleware/versioner/header_spec.rb +6 -0
  79. data/spec/grape/named_api_spec.rb +19 -0
  80. data/spec/grape/request_spec.rb +24 -0
  81. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +29 -0
  82. data/spec/grape/validations/params_scope_spec.rb +184 -8
  83. data/spec/grape/validations/single_attribute_iterator_spec.rb +33 -0
  84. data/spec/grape/validations/validators/all_or_none_spec.rb +138 -30
  85. data/spec/grape/validations/validators/at_least_one_of_spec.rb +173 -29
  86. data/spec/grape/validations/validators/coerce_spec.rb +10 -2
  87. data/spec/grape/validations/validators/exactly_one_of_spec.rb +202 -38
  88. data/spec/grape/validations/validators/mutual_exclusion_spec.rb +184 -27
  89. data/spec/grape/validations/validators/same_as_spec.rb +63 -0
  90. data/spec/grape/validations_spec.rb +33 -21
  91. data/spec/spec_helper.rb +4 -1
  92. metadata +35 -23
  93. data/Appraisals +0 -32
  94. data/Dangerfile +0 -2
  95. data/Gemfile +0 -33
  96. data/Gemfile.lock +0 -231
  97. data/Guardfile +0 -10
  98. data/RELEASING.md +0 -111
  99. data/Rakefile +0 -25
  100. data/benchmark/simple.rb +0 -27
  101. data/benchmark/simple_with_type_coercer.rb +0 -22
  102. data/gemfiles/multi_json.gemfile +0 -35
  103. data/gemfiles/multi_xml.gemfile +0 -35
  104. data/gemfiles/rack_1.5.2.gemfile +0 -35
  105. data/gemfiles/rack_edge.gemfile +0 -35
  106. data/gemfiles/rails_3.gemfile +0 -36
  107. data/gemfiles/rails_4.gemfile +0 -35
  108. data/gemfiles/rails_5.gemfile +0 -35
  109. data/gemfiles/rails_edge.gemfile +0 -35
  110. data/pkg/grape-0.17.0.gem +0 -0
  111. data/pkg/grape-0.19.0.gem +0 -0
@@ -62,6 +62,30 @@ module Grape
62
62
  end
63
63
  end
64
64
 
65
+ describe 'when the param_builder is set to Hashie' do
66
+ before do
67
+ Grape.configure do |config|
68
+ config.param_builder = Grape::Extensions::Hashie::Mash::ParamBuilder
69
+ end
70
+ end
71
+
72
+ after do
73
+ Grape.config.reset
74
+ end
75
+
76
+ subject(:request_params) { Grape::Request.new(env, opts).params }
77
+
78
+ context 'when the API does not include a specific param builder' do
79
+ let(:opts) { {} }
80
+ it { is_expected.to be_a(Hashie::Mash) }
81
+ end
82
+
83
+ context 'when the API includes a specific param builder' do
84
+ let(:opts) { { build_params_with: Grape::Extensions::Hash::ParamBuilder } }
85
+ it { is_expected.to be_a(Hash) }
86
+ end
87
+ end
88
+
65
89
  describe '#headers' do
66
90
  let(:options) do
67
91
  default_options.merge(request_headers)
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ describe Grape::Validations::MultipleAttributesIterator do
4
+ describe '#each' do
5
+ subject(:iterator) { described_class.new(validator, scope, params) }
6
+ let(:scope) { Grape::Validations::ParamsScope.new(api: Class.new(Grape::API)) }
7
+ let(:validator) { double(attrs: %i[first second third]) }
8
+
9
+ context 'when params is a hash' do
10
+ let(:params) do
11
+ { first: 'string', second: 'string' }
12
+ end
13
+
14
+ it 'yields the whole params hash without the list of attrs' do
15
+ expect { |b| iterator.each(&b) }.to yield_with_args(params)
16
+ end
17
+ end
18
+
19
+ context 'when params is an array' do
20
+ let(:params) do
21
+ [{ first: 'string1', second: 'string1' }, { first: 'string2', second: 'string2' }]
22
+ end
23
+
24
+ it 'yields each element of the array without the list of attrs' do
25
+ expect { |b| iterator.each(&b) }.to yield_successive_args(params[0], params[1])
26
+ end
27
+ end
28
+ end
29
+ end
@@ -121,14 +121,14 @@ describe Grape::Validations::ParamsScope do
121
121
  end
122
122
  end
123
123
 
124
- context 'param alias' do
124
+ context 'param renaming' do
125
125
  it do
126
126
  subject.params do
127
127
  requires :foo, as: :bar
128
128
  optional :super, as: :hiper
129
129
  end
130
- subject.get('/alias') { "#{declared(params)['bar']}-#{declared(params)['hiper']}" }
131
- get '/alias', foo: 'any', super: 'any2'
130
+ subject.get('/renaming') { "#{declared(params)['bar']}-#{declared(params)['hiper']}" }
131
+ get '/renaming', foo: 'any', super: 'any2'
132
132
 
133
133
  expect(last_response.status).to eq(200)
134
134
  expect(last_response.body).to eq('any-any2')
@@ -138,8 +138,8 @@ describe Grape::Validations::ParamsScope do
138
138
  subject.params do
139
139
  requires :foo, as: :bar, type: String, coerce_with: ->(c) { c.strip }
140
140
  end
141
- subject.get('/alias-coerced') { "#{params['bar']}-#{params['foo']}" }
142
- get '/alias-coerced', foo: ' there we go '
141
+ subject.get('/renaming-coerced') { "#{params['bar']}-#{params['foo']}" }
142
+ get '/renaming-coerced', foo: ' there we go '
143
143
 
144
144
  expect(last_response.status).to eq(200)
145
145
  expect(last_response.body).to eq('there we go-')
@@ -149,12 +149,35 @@ describe Grape::Validations::ParamsScope do
149
149
  subject.params do
150
150
  requires :foo, as: :bar, allow_blank: false
151
151
  end
152
- subject.get('/alias-not-blank') {}
153
- get '/alias-not-blank', foo: ''
152
+ subject.get('/renaming-not-blank') {}
153
+ get '/renaming-not-blank', foo: ''
154
154
 
155
155
  expect(last_response.status).to eq(400)
156
156
  expect(last_response.body).to eq('foo is empty')
157
157
  end
158
+
159
+ it do
160
+ subject.params do
161
+ requires :foo, as: :bar, allow_blank: false
162
+ end
163
+ subject.get('/renaming-not-blank-with-value') {}
164
+ get '/renaming-not-blank-with-value', foo: 'any'
165
+
166
+ expect(last_response.status).to eq(200)
167
+ end
168
+
169
+ it do
170
+ subject.params do
171
+ requires :foo, as: :baz, type: Hash do
172
+ requires :bar, as: :qux
173
+ end
174
+ end
175
+ subject.get('/nested-renaming') { declared(params).to_json }
176
+ get '/nested-renaming', foo: { bar: 'any' }
177
+
178
+ expect(last_response.status).to eq(200)
179
+ expect(last_response.body).to eq('{"baz":{"qux":"any"}}')
180
+ end
158
181
  end
159
182
 
160
183
  context 'array without coerce type explicitly given' do
@@ -479,7 +502,50 @@ describe Grape::Validations::ParamsScope do
479
502
  end.to_not raise_error
480
503
  end
481
504
 
482
- it 'allows aliasing of dependent parameters' do
505
+ it 'does not raise an error if when using nested given' do
506
+ expect do
507
+ subject.params do
508
+ optional :a, type: Hash do
509
+ requires :b
510
+ end
511
+ given :a do
512
+ requires :c
513
+ given :c do
514
+ requires :d
515
+ end
516
+ end
517
+ end
518
+ end.to_not raise_error
519
+ end
520
+
521
+ it 'allows nested dependent parameters' do
522
+ subject.params do
523
+ optional :a
524
+ given a: ->(val) { val == 'a' } do
525
+ optional :b
526
+ given b: ->(val) { val == 'b' } do
527
+ optional :c
528
+ given c: ->(val) { val == 'c' } do
529
+ requires :d
530
+ end
531
+ end
532
+ end
533
+ end
534
+ subject.get('/') { declared(params).to_json }
535
+
536
+ get '/'
537
+ expect(last_response.status).to eq 200
538
+
539
+ get '/', a: 'a', b: 'b', c: 'c'
540
+ expect(last_response.status).to eq 400
541
+ expect(last_response.body).to eq 'd is missing'
542
+
543
+ get '/', a: 'a', b: 'b', c: 'c', d: 'd'
544
+ expect(last_response.status).to eq 200
545
+ expect(last_response.body).to eq({ a: 'a', b: 'b', c: 'c', d: 'd' }.to_json)
546
+ end
547
+
548
+ it 'allows renaming of dependent parameters' do
483
549
  subject.params do
484
550
  optional :a
485
551
  given :a do
@@ -497,6 +563,34 @@ describe Grape::Validations::ParamsScope do
497
563
  expect(body.keys).to_not include('b')
498
564
  end
499
565
 
566
+ it 'allows renaming of dependent on parameter' do
567
+ subject.params do
568
+ optional :a, as: :b
569
+ given b: ->(val) { val == 'x' } do
570
+ requires :c
571
+ end
572
+ end
573
+ subject.get('/') { declared(params) }
574
+
575
+ get '/', a: 'x'
576
+ expect(last_response.status).to eq 400
577
+ expect(last_response.body).to eq 'c is missing'
578
+
579
+ get '/', a: 'y'
580
+ expect(last_response.status).to eq 200
581
+ end
582
+
583
+ it 'raises an error if the dependent parameter is not the renamed one' do
584
+ expect do
585
+ subject.params do
586
+ optional :a, as: :b
587
+ given :a do
588
+ requires :c
589
+ end
590
+ end
591
+ end.to raise_error(Grape::Exceptions::UnknownParameter)
592
+ end
593
+
500
594
  it 'does not validate nested requires when given is false' do
501
595
  subject.params do
502
596
  requires :a, type: String, allow_blank: false, values: %w[x y z]
@@ -592,7 +686,53 @@ describe Grape::Validations::ParamsScope do
592
686
  end
593
687
  end
594
688
 
689
+ context 'default value in given block' do
690
+ before do
691
+ subject.params do
692
+ optional :a, values: %w[a b]
693
+ given a: ->(val) { val == 'a' } do
694
+ optional :b, default: 'default'
695
+ end
696
+ end
697
+ subject.get('/') { params.to_json }
698
+ end
699
+
700
+ context 'when dependency meets' do
701
+ it 'sets default value for dependent parameter' do
702
+ get '/', a: 'a'
703
+ expect(last_response.body).to eq({ a: 'a', b: 'default' }.to_json)
704
+ end
705
+ end
706
+
707
+ context 'when dependency does not meet' do
708
+ it 'does not set default value for dependent parameter' do
709
+ get '/', a: 'b'
710
+ expect(last_response.body).to eq({ a: 'b' }.to_json)
711
+ end
712
+ end
713
+ end
714
+
595
715
  context 'when validations are dependent on a parameter within an array param' do
716
+ before do
717
+ subject.params do
718
+ requires :foos, type: Array do
719
+ optional :foo
720
+ given :foo do
721
+ requires :bar
722
+ end
723
+ end
724
+ end
725
+ subject.get('/test') { 'ok' }
726
+ end
727
+
728
+ it 'should pass none Hash params' do
729
+ get '/test', foos: ['']
730
+ expect(last_response.status).to eq(200)
731
+ expect(last_response.body).to eq('ok')
732
+ end
733
+ end
734
+
735
+ context 'when validations are dependent on a parameter within an array param within #declared(params).to_json' do
596
736
  before do
597
737
  subject.params do
598
738
  requires :foos, type: Array do
@@ -948,4 +1088,40 @@ describe Grape::Validations::ParamsScope do
948
1088
  end
949
1089
  end
950
1090
  end
1091
+
1092
+ context 'with exactly_one_of validation for optional parameters within an Hash param' do
1093
+ before do
1094
+ subject.params do
1095
+ optional :memo, type: Hash do
1096
+ optional :text, type: String
1097
+ optional :custom_body, type: Hash, coerce_with: JSON
1098
+ exactly_one_of :text, :custom_body
1099
+ end
1100
+ end
1101
+ subject.get('test')
1102
+ end
1103
+
1104
+ context 'when correct data is provided' do
1105
+ it 'returns a successful response' do
1106
+ get 'test', memo: {}
1107
+ expect(last_response.status).to eq(200)
1108
+
1109
+ get 'test', memo: { text: 'HOGEHOGE' }
1110
+ expect(last_response.status).to eq(200)
1111
+
1112
+ get 'test', memo: { custom_body: '{ "xxx": "yyy" }' }
1113
+ expect(last_response.status).to eq(200)
1114
+ end
1115
+ end
1116
+
1117
+ context 'when invalid data is provided' do
1118
+ it 'returns a failure response' do
1119
+ get 'test', memo: { text: 'HOGEHOGE', custom_body: '{ "xxx": "yyy" }' }
1120
+ expect(last_response.status).to eq(400)
1121
+
1122
+ get 'test', memo: '{ "custom_body": "HOGE" }'
1123
+ expect(last_response.status).to eq(400)
1124
+ end
1125
+ end
1126
+ end
951
1127
  end
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+
3
+ describe Grape::Validations::SingleAttributeIterator do
4
+ describe '#each' do
5
+ subject(:iterator) { described_class.new(validator, scope, params) }
6
+ let(:scope) { Grape::Validations::ParamsScope.new(api: Class.new(Grape::API)) }
7
+ let(:validator) { double(attrs: %i[first second third]) }
8
+
9
+ context 'when params is a hash' do
10
+ let(:params) do
11
+ { first: 'string', second: 'string' }
12
+ end
13
+
14
+ it 'yields params and every single attribute from the list' do
15
+ expect { |b| iterator.each(&b) }
16
+ .to yield_successive_args([params, :first], [params, :second], [params, :third])
17
+ end
18
+ end
19
+
20
+ context 'when params is an array' do
21
+ let(:params) do
22
+ [{ first: 'string1', second: 'string1' }, { first: 'string2', second: 'string2' }]
23
+ end
24
+
25
+ it 'yields every single attribute from the list for each of the array elements' do
26
+ expect { |b| iterator.each(&b) }.to yield_successive_args(
27
+ [params[0], :first], [params[0], :second], [params[0], :third],
28
+ [params[1], :first], [params[1], :second], [params[1], :third]
29
+ )
30
+ end
31
+ end
32
+ end
33
+ end
@@ -2,58 +2,166 @@ require 'spec_helper'
2
2
 
3
3
  describe Grape::Validations::AllOrNoneOfValidator do
4
4
  describe '#validate!' do
5
- let(:scope) do
6
- Struct.new(:opts) do
7
- def params(arg)
8
- arg
9
- end
5
+ subject(:validate) { post path, params }
6
+
7
+ module ValidationsSpec
8
+ module AllOrNoneOfValidatorSpec
9
+ class API < Grape::API
10
+ rescue_from Grape::Exceptions::ValidationErrors do |e|
11
+ error!(e.errors.transform_keys! { |key| key.join(',') }, 400)
12
+ end
13
+
14
+ params do
15
+ optional :beer, :wine, type: Boolean
16
+ all_or_none_of :beer, :wine
17
+ end
18
+ post do
19
+ end
20
+
21
+ params do
22
+ optional :beer, :wine, :other, type: Boolean
23
+ all_or_none_of :beer, :wine
24
+ end
25
+ post 'mixed-params' do
26
+ end
27
+
28
+ params do
29
+ optional :beer, :wine, type: Boolean
30
+ all_or_none_of :beer, :wine, message: 'choose all or none'
31
+ end
32
+ post '/custom-message' do
33
+ end
34
+
35
+ params do
36
+ requires :item, type: Hash do
37
+ optional :beer, :wine, type: Boolean
38
+ all_or_none_of :beer, :wine
39
+ end
40
+ end
41
+ post '/nested-hash' do
42
+ end
43
+
44
+ params do
45
+ requires :items, type: Array do
46
+ optional :beer, :wine, type: Boolean
47
+ all_or_none_of :beer, :wine
48
+ end
49
+ end
50
+ post '/nested-array' do
51
+ end
10
52
 
11
- def required?; end
53
+ params do
54
+ requires :items, type: Array do
55
+ requires :nested_items, type: Array do
56
+ optional :beer, :wine, type: Boolean
57
+ all_or_none_of :beer, :wine
58
+ end
59
+ end
60
+ end
61
+ post '/deeply-nested-array' do
62
+ end
63
+ end
12
64
  end
13
65
  end
14
- let(:all_or_none_params) { %i[beer wine grapefruit] }
15
- let(:validator) { described_class.new(all_or_none_params, {}, false, scope.new) }
66
+
67
+ def app
68
+ ValidationsSpec::AllOrNoneOfValidatorSpec::API
69
+ end
16
70
 
17
71
  context 'when all restricted params are present' do
18
- let(:params) { { beer: true, wine: true, grapefruit: true } }
72
+ let(:path) { '/' }
73
+ let(:params) { { beer: true, wine: true } }
19
74
 
20
- it 'does not raise a validation exception' do
21
- expect(validator.validate!(params)).to eql params
75
+ it 'does not return a validation error' do
76
+ validate
77
+ expect(last_response.status).to eq 201
22
78
  end
23
79
 
24
80
  context 'mixed with other params' do
25
- let(:mixed_params) { params.merge!(other: true, andanother: true) }
81
+ let(:path) { '/mixed-params' }
82
+ let(:params) { { beer: true, wine: true, other: true } }
26
83
 
27
- it 'does not raise a validation exception' do
28
- expect(validator.validate!(mixed_params)).to eql mixed_params
84
+ it 'does not return a validation error' do
85
+ validate
86
+ expect(last_response.status).to eq 201
29
87
  end
30
88
  end
31
89
  end
32
90
 
33
- context 'when none of the restricted params is selected' do
91
+ context 'when a subset of restricted params are present' do
92
+ let(:path) { '/' }
93
+ let(:params) { { beer: true } }
94
+
95
+ it 'returns a validation error' do
96
+ validate
97
+ expect(last_response.status).to eq 400
98
+ expect(JSON.parse(last_response.body)).to eq(
99
+ 'beer,wine' => ['provide all or none of parameters']
100
+ )
101
+ end
102
+ end
103
+
104
+ context 'when custom message is specified' do
105
+ let(:path) { '/custom-message' }
106
+ let(:params) { { beer: true } }
107
+
108
+ it 'returns a validation error' do
109
+ validate
110
+ expect(last_response.status).to eq 400
111
+ expect(JSON.parse(last_response.body)).to eq(
112
+ 'beer,wine' => ['choose all or none']
113
+ )
114
+ end
115
+ end
116
+
117
+ context 'when no restricted params are present' do
118
+ let(:path) { '/' }
34
119
  let(:params) { { somethingelse: true } }
35
120
 
36
- it 'does not raise a validation exception' do
37
- expect(validator.validate!(params)).to eql params
121
+ it 'does not return a validation error' do
122
+ validate
123
+ expect(last_response.status).to eq 201
38
124
  end
39
125
  end
40
126
 
41
- context 'when only a subset of restricted params are present' do
42
- let(:params) { { beer: true, grapefruit: true } }
127
+ context 'when restricted params are nested inside required hash' do
128
+ let(:path) { '/nested-hash' }
129
+ let(:params) { { item: { beer: true } } }
43
130
 
44
- it 'raises a validation exception' do
45
- expect do
46
- validator.validate! params
47
- end.to raise_error(Grape::Exceptions::Validation)
131
+ it 'returns a validation error with full names of the params' do
132
+ validate
133
+ expect(last_response.status).to eq 400
134
+ expect(JSON.parse(last_response.body)).to eq(
135
+ 'item[beer],item[wine]' => ['provide all or none of parameters']
136
+ )
48
137
  end
49
- context 'mixed with other params' do
50
- let(:mixed_params) { params.merge!(other: true, andanother: true) }
138
+ end
51
139
 
52
- it 'raise a validation exception' do
53
- expect do
54
- validator.validate! params
55
- end.to raise_error(Grape::Exceptions::Validation)
56
- end
140
+ context 'when mutually exclusive params are nested inside array' do
141
+ let(:path) { '/nested-array' }
142
+ let(:params) { { items: [{ beer: true, wine: true }, { wine: true }] } }
143
+
144
+ it 'returns a validation error with full names of the params' do
145
+ validate
146
+ expect(last_response.status).to eq 400
147
+ expect(JSON.parse(last_response.body)).to eq(
148
+ 'items[1][beer],items[1][wine]' => ['provide all or none of parameters']
149
+ )
150
+ end
151
+ end
152
+
153
+ context 'when mutually exclusive params are deeply nested' do
154
+ let(:path) { '/deeply-nested-array' }
155
+ let(:params) { { items: [{ nested_items: [{ beer: true }] }] } }
156
+
157
+ it 'returns a validation error with full names of the params' do
158
+ validate
159
+ expect(last_response.status).to eq 400
160
+ expect(JSON.parse(last_response.body)).to eq(
161
+ 'items[0][nested_items][0][beer],items[0][nested_items][0][wine]' => [
162
+ 'provide all or none of parameters'
163
+ ]
164
+ )
57
165
  end
58
166
  end
59
167
  end