grape 1.2.4 → 1.2.5

Sign up to get free protection for your applications and to get access to all the features.
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