grape 1.1.0 → 1.2.5

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 (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