grape 1.2.4 → 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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -4
  3. data/README.md +144 -4
  4. data/grape.gemspec +3 -1
  5. data/lib/grape.rb +94 -66
  6. data/lib/grape/api.rb +46 -4
  7. data/lib/grape/api/instance.rb +23 -12
  8. data/lib/grape/dsl/desc.rb +11 -2
  9. data/lib/grape/dsl/validations.rb +4 -3
  10. data/lib/grape/eager_load.rb +18 -0
  11. data/lib/grape/endpoint.rb +3 -3
  12. data/lib/grape/error_formatter.rb +1 -1
  13. data/lib/grape/exceptions/validation_errors.rb +4 -2
  14. data/lib/grape/formatter.rb +1 -1
  15. data/lib/grape/middleware/auth/base.rb +2 -4
  16. data/lib/grape/middleware/base.rb +2 -0
  17. data/lib/grape/middleware/helpers.rb +10 -0
  18. data/lib/grape/parser.rb +1 -1
  19. data/lib/grape/util/base_inheritable.rb +34 -0
  20. data/lib/grape/util/inheritable_values.rb +5 -25
  21. data/lib/grape/util/lazy_block.rb +25 -0
  22. data/lib/grape/util/lazy_value.rb +5 -0
  23. data/lib/grape/util/reverse_stackable_values.rb +7 -36
  24. data/lib/grape/util/stackable_values.rb +19 -22
  25. data/lib/grape/validations/attributes_iterator.rb +5 -3
  26. data/lib/grape/validations/multiple_attributes_iterator.rb +11 -0
  27. data/lib/grape/validations/params_scope.rb +12 -12
  28. data/lib/grape/validations/single_attribute_iterator.rb +13 -0
  29. data/lib/grape/validations/validator_factory.rb +6 -11
  30. data/lib/grape/validations/validators/all_or_none.rb +6 -13
  31. data/lib/grape/validations/validators/at_least_one_of.rb +5 -13
  32. data/lib/grape/validations/validators/base.rb +11 -10
  33. data/lib/grape/validations/validators/coerce.rb +4 -0
  34. data/lib/grape/validations/validators/default.rb +1 -1
  35. data/lib/grape/validations/validators/exactly_one_of.rb +6 -23
  36. data/lib/grape/validations/validators/multiple_params_base.rb +14 -10
  37. data/lib/grape/validations/validators/mutual_exclusion.rb +6 -18
  38. data/lib/grape/version.rb +1 -1
  39. data/spec/grape/api/defines_boolean_in_params_spec.rb +37 -0
  40. data/spec/grape/api_remount_spec.rb +158 -0
  41. data/spec/grape/api_spec.rb +72 -0
  42. data/spec/grape/endpoint_spec.rb +1 -1
  43. data/spec/grape/exceptions/base_spec.rb +4 -0
  44. data/spec/grape/exceptions/validation_errors_spec.rb +6 -4
  45. data/spec/grape/integration/rack_spec.rb +22 -6
  46. data/spec/grape/middleware/base_spec.rb +8 -0
  47. data/spec/grape/middleware/formatter_spec.rb +11 -1
  48. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +29 -0
  49. data/spec/grape/validations/params_scope_spec.rb +13 -0
  50. data/spec/grape/validations/single_attribute_iterator_spec.rb +33 -0
  51. data/spec/grape/validations/validators/all_or_none_spec.rb +138 -30
  52. data/spec/grape/validations/validators/at_least_one_of_spec.rb +173 -29
  53. data/spec/grape/validations/validators/coerce_spec.rb +6 -2
  54. data/spec/grape/validations/validators/exactly_one_of_spec.rb +202 -38
  55. data/spec/grape/validations/validators/mutual_exclusion_spec.rb +184 -27
  56. data/spec/grape/validations_spec.rb +32 -20
  57. metadata +103 -115
  58. data/Appraisals +0 -28
  59. data/Dangerfile +0 -2
  60. data/Gemfile +0 -33
  61. data/Gemfile.lock +0 -231
  62. data/Guardfile +0 -10
  63. data/RELEASING.md +0 -111
  64. data/Rakefile +0 -25
  65. data/benchmark/simple.rb +0 -27
  66. data/benchmark/simple_with_type_coercer.rb +0 -22
  67. data/gemfiles/multi_json.gemfile +0 -35
  68. data/gemfiles/multi_xml.gemfile +0 -35
  69. data/gemfiles/rack_1.5.2.gemfile.lock +0 -232
  70. data/gemfiles/rack_edge.gemfile +0 -35
  71. data/gemfiles/rails_3.gemfile +0 -36
  72. data/gemfiles/rails_3.gemfile.lock +0 -288
  73. data/gemfiles/rails_4.gemfile +0 -35
  74. data/gemfiles/rails_4.gemfile.lock +0 -280
  75. data/gemfiles/rails_5.gemfile +0 -35
  76. data/gemfiles/rails_5.gemfile.lock +0 -312
  77. data/gemfiles/rails_edge.gemfile +0 -35
  78. data/pkg/grape-1.2.0.gem +0 -0
  79. data/pkg/grape-1.2.1.gem +0 -0
  80. data/pkg/grape-1.2.3.gem +0 -0
@@ -165,6 +165,19 @@ describe Grape::Validations::ParamsScope do
165
165
 
166
166
  expect(last_response.status).to eq(200)
167
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
168
181
  end
169
182
 
170
183
  context 'array without coerce type explicitly given' do
@@ -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
@@ -2,65 +2,209 @@ require 'spec_helper'
2
2
 
3
3
  describe Grape::Validations::AtLeastOneOfValidator 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 AtLeastOneOfValidatorSpec
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, :grapefruit
16
+ at_least_one_of :beer, :wine, :grapefruit
17
+ end
18
+ post do
19
+ end
20
+
21
+ params do
22
+ optional :beer, :wine, :grapefruit, :other
23
+ at_least_one_of :beer, :wine, :grapefruit
24
+ end
25
+ post 'mixed-params' do
26
+ end
27
+
28
+ params do
29
+ optional :beer, :wine, :grapefruit
30
+ at_least_one_of :beer, :wine, :grapefruit, message: 'you should choose something'
31
+ end
32
+ post '/custom-message' do
33
+ end
34
+
35
+ params do
36
+ requires :item, type: Hash do
37
+ optional :beer, :wine, :grapefruit
38
+ at_least_one_of :beer, :wine, :grapefruit, message: 'fail'
39
+ end
40
+ end
41
+ post '/nested-hash' do
42
+ end
10
43
 
11
- def required?; end
44
+ params do
45
+ requires :items, type: Array do
46
+ optional :beer, :wine, :grapefruit
47
+ at_least_one_of :beer, :wine, :grapefruit, message: 'fail'
48
+ end
49
+ end
50
+ post '/nested-array' do
51
+ end
52
+
53
+ params do
54
+ requires :items, type: Array do
55
+ requires :nested_items, type: Array do
56
+ optional :beer, :wine, :grapefruit
57
+ at_least_one_of :beer, :wine, :grapefruit, message: 'fail'
58
+ end
59
+ end
60
+ end
61
+ post '/deeply-nested-array' do
62
+ end
63
+ end
12
64
  end
13
65
  end
14
- let(:at_least_one_of_params) { %i[beer wine grapefruit] }
15
- let(:validator) { described_class.new(at_least_one_of_params, {}, false, scope.new) }
66
+
67
+ def app
68
+ ValidationsSpec::AtLeastOneOfValidatorSpec::API
69
+ end
16
70
 
17
71
  context 'when all restricted params are present' do
72
+ let(:path) { '/' }
18
73
  let(:params) { { beer: true, wine: true, grapefruit: 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, grapefruit: 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
91
  context 'when a subset of restricted params are present' do
92
+ let(:path) { '/' }
34
93
  let(:params) { { beer: true, grapefruit: true } }
35
94
 
36
- it 'does not raise a validation exception' do
37
- expect(validator.validate!(params)).to eql params
95
+ it 'does not return a validation error' do
96
+ validate
97
+ expect(last_response.status).to eq 201
38
98
  end
39
99
  end
40
100
 
41
- context 'when params keys come as strings' do
42
- let(:params) { { 'beer' => true, 'grapefruit' => true } }
101
+ context 'when none of the restricted params is selected' do
102
+ let(:path) { '/' }
103
+ let(:params) { { other: true } }
104
+
105
+ it 'returns a validation error' do
106
+ validate
107
+ expect(last_response.status).to eq 400
108
+ expect(JSON.parse(last_response.body)).to eq(
109
+ 'beer,wine,grapefruit' => ['are missing, at least one parameter must be provided']
110
+ )
111
+ end
112
+
113
+ context 'when custom message is specified' do
114
+ let(:path) { '/custom-message' }
43
115
 
44
- it 'does not raise a validation exception' do
45
- expect(validator.validate!(params)).to eql params
116
+ it 'returns a validation error' do
117
+ validate
118
+ expect(last_response.status).to eq 400
119
+ expect(JSON.parse(last_response.body)).to eq(
120
+ 'beer,wine,grapefruit' => ['you should choose something']
121
+ )
122
+ end
46
123
  end
47
124
  end
48
125
 
49
- context 'when none of the restricted params is selected' do
50
- let(:params) { { somethingelse: true } }
126
+ context 'when exactly one of the restricted params is selected' do
127
+ let(:path) { '/' }
128
+ let(:params) { { beer: true } }
51
129
 
52
- it 'raises a validation exception' do
53
- expect do
54
- validator.validate! params
55
- end.to raise_error(Grape::Exceptions::Validation)
130
+ it 'does not return a validation error' do
131
+ validate
132
+ expect(last_response.status).to eq 201
56
133
  end
57
134
  end
58
135
 
59
- context 'when exactly one of the restricted params is selected' do
60
- let(:params) { { beer: true, somethingelse: true } }
136
+ context 'when restricted params are nested inside hash' do
137
+ let(:path) { '/nested-hash' }
138
+
139
+ context 'when at least one of them is present' do
140
+ let(:params) { { item: { beer: true, wine: true } } }
141
+
142
+ it 'does not return a validation error' do
143
+ validate
144
+ expect(last_response.status).to eq 201
145
+ end
146
+ end
61
147
 
62
- it 'does not raise a validation exception' do
63
- expect(validator.validate!(params)).to eql params
148
+ context 'when none of them are present' do
149
+ let(:params) { { item: { other: true } } }
150
+
151
+ it 'returns a validation error with full names of the params' do
152
+ validate
153
+ expect(last_response.status).to eq 400
154
+ expect(JSON.parse(last_response.body)).to eq(
155
+ 'item[beer],item[wine],item[grapefruit]' => ['fail']
156
+ )
157
+ end
158
+ end
159
+ end
160
+
161
+ context 'when restricted params are nested inside array' do
162
+ let(:path) { '/nested-array' }
163
+
164
+ context 'when at least one of them is present' do
165
+ let(:params) { { items: [{ beer: true, wine: true }, { grapefruit: true }] } }
166
+
167
+ it 'does not return a validation error' do
168
+ validate
169
+ expect(last_response.status).to eq 201
170
+ end
171
+ end
172
+
173
+ context 'when none of them are present' do
174
+ let(:params) { { items: [{ beer: true, other: true }, { other: true }] } }
175
+
176
+ it 'returns a validation error with full names of the params' do
177
+ validate
178
+ expect(last_response.status).to eq 400
179
+ expect(JSON.parse(last_response.body)).to eq(
180
+ 'items[1][beer],items[1][wine],items[1][grapefruit]' => ['fail']
181
+ )
182
+ end
183
+ end
184
+ end
185
+
186
+ context 'when restricted params are deeply nested' do
187
+ let(:path) { '/deeply-nested-array' }
188
+
189
+ context 'when at least one of them is present' do
190
+ let(:params) { { items: [{ nested_items: [{ wine: true }] }] } }
191
+
192
+ it 'does not return a validation error' do
193
+ validate
194
+ expect(last_response.status).to eq 201
195
+ end
196
+ end
197
+
198
+ context 'when none of them are present' do
199
+ let(:params) { { items: [{ nested_items: [{ other: true }] }] } }
200
+
201
+ it 'returns a validation error with full names of the params' do
202
+ validate
203
+ expect(last_response.status).to eq 400
204
+ expect(JSON.parse(last_response.body)).to eq(
205
+ 'items[0][nested_items][0][beer],items[0][nested_items][0][wine],items[0][nested_items][0][grapefruit]' => ['fail']
206
+ )
207
+ end
64
208
  end
65
209
  end
66
210
  end