grape 0.3.0 → 0.7.0
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.
Potentially problematic release.
This version of grape might be problematic. Click here for more details.
- checksums.yaml +15 -0
- data/.gitignore +8 -0
- data/.rubocop.yml +70 -0
- data/.travis.yml +7 -6
- data/CHANGELOG.md +134 -4
- data/CONTRIBUTING.md +118 -0
- data/Gemfile +5 -2
- data/README.md +551 -116
- data/RELEASING.md +105 -0
- data/Rakefile +29 -8
- data/UPGRADING.md +124 -0
- data/grape.gemspec +3 -3
- data/lib/grape/api.rb +207 -88
- data/lib/grape/cookies.rb +4 -8
- data/lib/grape/endpoint.rb +198 -144
- data/lib/grape/error_formatter/base.rb +5 -7
- data/lib/grape/error_formatter/json.rb +3 -5
- data/lib/grape/error_formatter/txt.rb +1 -3
- data/lib/grape/error_formatter/xml.rb +4 -6
- data/lib/grape/exceptions/base.rb +9 -9
- data/lib/grape/exceptions/incompatible_option_values.rb +10 -0
- data/lib/grape/exceptions/invalid_formatter.rb +1 -4
- data/lib/grape/exceptions/invalid_versioner_option.rb +1 -5
- data/lib/grape/exceptions/invalid_with_option_for_represent.rb +1 -6
- data/lib/grape/exceptions/missing_mime_type.rb +1 -5
- data/lib/grape/exceptions/missing_option.rb +1 -4
- data/lib/grape/exceptions/missing_vendor_option.rb +1 -4
- data/lib/grape/exceptions/unknown_options.rb +1 -5
- data/lib/grape/exceptions/unknown_validator.rb +1 -3
- data/lib/grape/exceptions/validation.rb +13 -3
- data/lib/grape/exceptions/validation_errors.rb +43 -0
- data/lib/grape/formatter/base.rb +5 -7
- data/lib/grape/formatter/json.rb +0 -3
- data/lib/grape/formatter/serializable_hash.rb +15 -15
- data/lib/grape/formatter/txt.rb +0 -2
- data/lib/grape/formatter/xml.rb +0 -2
- data/lib/grape/http/request.rb +26 -0
- data/lib/grape/locale/en.yml +8 -5
- data/lib/grape/middleware/auth/base.rb +30 -0
- data/lib/grape/middleware/auth/basic.rb +3 -20
- data/lib/grape/middleware/auth/digest.rb +2 -19
- data/lib/grape/middleware/auth/oauth2.rb +31 -24
- data/lib/grape/middleware/base.rb +7 -7
- data/lib/grape/middleware/error.rb +36 -22
- data/lib/grape/middleware/filter.rb +3 -3
- data/lib/grape/middleware/formatter.rb +99 -61
- data/lib/grape/middleware/globals.rb +13 -0
- data/lib/grape/middleware/versioner/accept_version_header.rb +67 -0
- data/lib/grape/middleware/versioner/header.rb +22 -16
- data/lib/grape/middleware/versioner/param.rb +9 -11
- data/lib/grape/middleware/versioner/path.rb +10 -13
- data/lib/grape/middleware/versioner.rb +3 -1
- data/lib/grape/namespace.rb +23 -0
- data/lib/grape/parser/base.rb +3 -5
- data/lib/grape/parser/json.rb +0 -2
- data/lib/grape/parser/xml.rb +0 -2
- data/lib/grape/path.rb +70 -0
- data/lib/grape/route.rb +10 -6
- data/lib/grape/util/content_types.rb +2 -1
- data/lib/grape/util/deep_merge.rb +5 -5
- data/lib/grape/util/hash_stack.rb +13 -2
- data/lib/grape/validations/coerce.rb +11 -10
- data/lib/grape/validations/default.rb +25 -0
- data/lib/grape/validations/presence.rb +7 -3
- data/lib/grape/validations/regexp.rb +2 -5
- data/lib/grape/validations/values.rb +17 -0
- data/lib/grape/validations.rb +161 -54
- data/lib/grape/version.rb +1 -1
- data/lib/grape.rb +19 -4
- data/spec/grape/api_spec.rb +897 -268
- data/spec/grape/endpoint_spec.rb +283 -66
- data/spec/grape/entity_spec.rb +132 -29
- data/spec/grape/exceptions/missing_mime_type_spec.rb +3 -9
- data/spec/grape/exceptions/validation_errors_spec.rb +19 -0
- data/spec/grape/middleware/auth/basic_spec.rb +8 -8
- data/spec/grape/middleware/auth/digest_spec.rb +5 -5
- data/spec/grape/middleware/auth/oauth2_spec.rb +81 -36
- data/spec/grape/middleware/base_spec.rb +8 -13
- data/spec/grape/middleware/error_spec.rb +13 -17
- data/spec/grape/middleware/exception_spec.rb +47 -27
- data/spec/grape/middleware/formatter_spec.rb +103 -41
- data/spec/grape/middleware/versioner/accept_version_header_spec.rb +121 -0
- data/spec/grape/middleware/versioner/header_spec.rb +76 -51
- data/spec/grape/middleware/versioner/param_spec.rb +18 -18
- data/spec/grape/middleware/versioner/path_spec.rb +6 -6
- data/spec/grape/middleware/versioner_spec.rb +5 -2
- data/spec/grape/path_spec.rb +229 -0
- data/spec/grape/util/hash_stack_spec.rb +31 -32
- data/spec/grape/validations/coerce_spec.rb +116 -51
- data/spec/grape/validations/default_spec.rb +123 -0
- data/spec/grape/validations/presence_spec.rb +42 -44
- data/spec/grape/validations/regexp_spec.rb +9 -9
- data/spec/grape/validations/values_spec.rb +138 -0
- data/spec/grape/validations/zh-CN.yml +4 -3
- data/spec/grape/validations_spec.rb +681 -48
- data/spec/shared/versioning_examples.rb +22 -6
- data/spec/spec_helper.rb +3 -2
- data/spec/support/basic_auth_encode_helpers.rb +0 -1
- data/spec/support/content_type_helpers.rb +11 -0
- data/spec/support/versioned_helpers.rb +13 -5
- metadata +34 -84
@@ -1,27 +1,39 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Grape::Validations do
|
4
|
+
|
4
5
|
subject { Class.new(Grape::API) }
|
5
|
-
|
6
|
+
|
7
|
+
def app
|
8
|
+
subject
|
9
|
+
end
|
6
10
|
|
7
11
|
describe 'params' do
|
8
12
|
context 'optional' do
|
9
13
|
it 'validates when params is present' do
|
10
|
-
subject.params
|
11
|
-
|
14
|
+
subject.params do
|
15
|
+
optional :a_number, regexp: /^[0-9]+$/
|
16
|
+
end
|
17
|
+
subject.get '/optional' do
|
18
|
+
'optional works!'
|
19
|
+
end
|
12
20
|
|
13
|
-
get '/optional',
|
21
|
+
get '/optional', a_number: 'string'
|
14
22
|
last_response.status.should == 400
|
15
|
-
last_response.body.should == '
|
23
|
+
last_response.body.should == 'a_number is invalid'
|
16
24
|
|
17
|
-
get '/optional',
|
25
|
+
get '/optional', a_number: 45
|
18
26
|
last_response.status.should == 200
|
19
27
|
last_response.body.should == 'optional works!'
|
20
28
|
end
|
21
29
|
|
22
30
|
it "doesn't validate when param not present" do
|
23
|
-
subject.params
|
24
|
-
|
31
|
+
subject.params do
|
32
|
+
optional :a_number, regexp: /^[0-9]+$/
|
33
|
+
end
|
34
|
+
subject.get '/optional' do
|
35
|
+
'optional works!'
|
36
|
+
end
|
25
37
|
|
26
38
|
get '/optional'
|
27
39
|
last_response.status.should == 200
|
@@ -29,56 +41,582 @@ describe Grape::Validations do
|
|
29
41
|
end
|
30
42
|
|
31
43
|
it 'adds to declared parameters' do
|
32
|
-
subject.params
|
44
|
+
subject.params do
|
45
|
+
optional :some_param
|
46
|
+
end
|
33
47
|
subject.settings[:declared_params].should == [:some_param]
|
34
48
|
end
|
35
49
|
end
|
36
50
|
|
37
51
|
context 'required' do
|
38
52
|
before do
|
39
|
-
subject.params
|
40
|
-
|
53
|
+
subject.params do
|
54
|
+
requires :key
|
55
|
+
end
|
56
|
+
subject.get '/required' do
|
57
|
+
'required works'
|
58
|
+
end
|
41
59
|
end
|
42
60
|
|
43
61
|
it 'errors when param not present' do
|
44
62
|
get '/required'
|
45
63
|
last_response.status.should == 400
|
46
|
-
last_response.body.should == '
|
64
|
+
last_response.body.should == 'key is missing'
|
47
65
|
end
|
48
66
|
|
49
67
|
it "doesn't throw a missing param when param is present" do
|
50
|
-
get '/required',
|
68
|
+
get '/required', key: 'cool'
|
51
69
|
last_response.status.should == 200
|
52
70
|
last_response.body.should == 'required works'
|
53
71
|
end
|
54
72
|
|
55
73
|
it 'adds to declared parameters' do
|
56
|
-
subject.params
|
74
|
+
subject.params do
|
75
|
+
requires :some_param
|
76
|
+
end
|
57
77
|
subject.settings[:declared_params].should == [:some_param]
|
58
78
|
end
|
59
79
|
end
|
60
80
|
|
81
|
+
context 'requires :all using Grape::Entity documentation' do
|
82
|
+
def define_requires_all
|
83
|
+
documentation = {
|
84
|
+
required_field: { type: String },
|
85
|
+
optional_field: { type: String }
|
86
|
+
}
|
87
|
+
subject.params do
|
88
|
+
requires :all, except: :optional_field, using: documentation
|
89
|
+
end
|
90
|
+
end
|
91
|
+
before do
|
92
|
+
define_requires_all
|
93
|
+
subject.get '/required' do
|
94
|
+
'required works'
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'adds entity documentation to declared params' do
|
99
|
+
define_requires_all
|
100
|
+
subject.settings[:declared_params].should == [:required_field, :optional_field]
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'errors when required_field is not present' do
|
104
|
+
get '/required'
|
105
|
+
last_response.status.should == 400
|
106
|
+
last_response.body.should == 'required_field is missing'
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'works when required_field is present' do
|
110
|
+
get '/required', required_field: 'woof'
|
111
|
+
last_response.status.should == 200
|
112
|
+
last_response.body.should == 'required works'
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
context 'requires :none using Grape::Entity documentation' do
|
117
|
+
def define_requires_none
|
118
|
+
documentation = {
|
119
|
+
required_field: { type: String },
|
120
|
+
optional_field: { type: String }
|
121
|
+
}
|
122
|
+
subject.params do
|
123
|
+
requires :none, except: :required_field, using: documentation
|
124
|
+
end
|
125
|
+
end
|
126
|
+
before do
|
127
|
+
define_requires_none
|
128
|
+
subject.get '/required' do
|
129
|
+
'required works'
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'adds entity documentation to declared params' do
|
134
|
+
define_requires_none
|
135
|
+
subject.settings[:declared_params].should == [:required_field, :optional_field]
|
136
|
+
end
|
137
|
+
|
138
|
+
it 'errors when required_field is not present' do
|
139
|
+
get '/required'
|
140
|
+
last_response.status.should == 400
|
141
|
+
last_response.body.should == 'required_field is missing'
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'works when required_field is present' do
|
145
|
+
get '/required', required_field: 'woof'
|
146
|
+
last_response.status.should == 200
|
147
|
+
last_response.body.should == 'required works'
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
context 'required with an Array block' do
|
152
|
+
before do
|
153
|
+
subject.params do
|
154
|
+
requires :items, type: Array do
|
155
|
+
requires :key
|
156
|
+
end
|
157
|
+
end
|
158
|
+
subject.get '/required' do
|
159
|
+
'required works'
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
it 'errors when param not present' do
|
164
|
+
get '/required'
|
165
|
+
last_response.status.should == 400
|
166
|
+
last_response.body.should == 'items is missing'
|
167
|
+
end
|
168
|
+
|
169
|
+
it "errors when param is not an Array" do
|
170
|
+
get '/required', items: "hello"
|
171
|
+
last_response.status.should == 400
|
172
|
+
last_response.body.should == 'items is invalid, items[key] is missing'
|
173
|
+
|
174
|
+
get '/required', items: { key: 'foo' }
|
175
|
+
last_response.status.should == 400
|
176
|
+
last_response.body.should == 'items is invalid'
|
177
|
+
end
|
178
|
+
|
179
|
+
it "doesn't throw a missing param when param is present" do
|
180
|
+
get '/required', items: [{ key: 'hello' }, { key: 'world' }]
|
181
|
+
last_response.status.should == 200
|
182
|
+
last_response.body.should == 'required works'
|
183
|
+
end
|
184
|
+
|
185
|
+
it "doesn't allow any key in the options hash other than type" do
|
186
|
+
expect {
|
187
|
+
subject.params do
|
188
|
+
requires(:items, desc: 'Foo') do
|
189
|
+
requires :key
|
190
|
+
end
|
191
|
+
end
|
192
|
+
}.to raise_error ArgumentError
|
193
|
+
end
|
194
|
+
|
195
|
+
it 'adds to declared parameters' do
|
196
|
+
subject.params do
|
197
|
+
requires :items do
|
198
|
+
requires :key
|
199
|
+
end
|
200
|
+
end
|
201
|
+
subject.settings[:declared_params].should == [items: [:key]]
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
context 'required with a Hash block' do
|
206
|
+
before do
|
207
|
+
subject.params do
|
208
|
+
requires :items, type: Hash do
|
209
|
+
requires :key
|
210
|
+
end
|
211
|
+
end
|
212
|
+
subject.get '/required' do
|
213
|
+
'required works'
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
it 'errors when param not present' do
|
218
|
+
get '/required'
|
219
|
+
last_response.status.should == 400
|
220
|
+
last_response.body.should == 'items is missing, items[key] is missing'
|
221
|
+
end
|
222
|
+
|
223
|
+
it "errors when param is not a Hash" do
|
224
|
+
get '/required', items: "hello"
|
225
|
+
last_response.status.should == 400
|
226
|
+
last_response.body.should == 'items is invalid, items[key] is missing'
|
227
|
+
|
228
|
+
get '/required', items: [{ key: 'foo' }]
|
229
|
+
last_response.status.should == 400
|
230
|
+
last_response.body.should == 'items is invalid'
|
231
|
+
end
|
232
|
+
|
233
|
+
it "doesn't throw a missing param when param is present" do
|
234
|
+
get '/required', items: { key: 'hello' }
|
235
|
+
last_response.status.should == 200
|
236
|
+
last_response.body.should == 'required works'
|
237
|
+
end
|
238
|
+
|
239
|
+
it "doesn't allow any key in the options hash other than type" do
|
240
|
+
expect {
|
241
|
+
subject.params do
|
242
|
+
requires(:items, desc: 'Foo') do
|
243
|
+
requires :key
|
244
|
+
end
|
245
|
+
end
|
246
|
+
}.to raise_error ArgumentError
|
247
|
+
end
|
248
|
+
|
249
|
+
it 'adds to declared parameters' do
|
250
|
+
subject.params do
|
251
|
+
requires :items do
|
252
|
+
requires :key
|
253
|
+
end
|
254
|
+
end
|
255
|
+
subject.settings[:declared_params].should == [items: [:key]]
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
61
259
|
context 'group' do
|
62
260
|
before do
|
63
|
-
subject.params
|
261
|
+
subject.params do
|
64
262
|
group :items do
|
65
263
|
requires :key
|
66
264
|
end
|
67
|
-
|
68
|
-
subject.get '/required' do
|
265
|
+
end
|
266
|
+
subject.get '/required' do
|
267
|
+
'required works'
|
268
|
+
end
|
69
269
|
end
|
70
270
|
|
71
271
|
it 'errors when param not present' do
|
72
272
|
get '/required'
|
73
273
|
last_response.status.should == 400
|
74
|
-
last_response.body.should == '
|
274
|
+
last_response.body.should == 'items is missing'
|
75
275
|
end
|
76
276
|
|
77
277
|
it "doesn't throw a missing param when param is present" do
|
78
|
-
get '/required',
|
278
|
+
get '/required', items: [key: 'hello', key: 'world']
|
79
279
|
last_response.status.should == 200
|
80
280
|
last_response.body.should == 'required works'
|
81
281
|
end
|
282
|
+
|
283
|
+
it 'adds to declared parameters' do
|
284
|
+
subject.params do
|
285
|
+
group :items do
|
286
|
+
requires :key
|
287
|
+
end
|
288
|
+
end
|
289
|
+
subject.settings[:declared_params].should == [items: [:key]]
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
context 'validation within arrays' do
|
294
|
+
before do
|
295
|
+
subject.params do
|
296
|
+
group :children do
|
297
|
+
requires :name
|
298
|
+
group :parents do
|
299
|
+
requires :name
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
subject.get '/within_array' do
|
304
|
+
'within array works'
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
it 'can handle new scopes within child elements' do
|
309
|
+
get '/within_array', children: [
|
310
|
+
{ name: 'John', parents: [{ name: 'Jane' }, { name: 'Bob' }] },
|
311
|
+
{ name: 'Joe', parents: [{ name: 'Josie' }] }
|
312
|
+
]
|
313
|
+
last_response.status.should == 200
|
314
|
+
last_response.body.should == 'within array works'
|
315
|
+
end
|
316
|
+
|
317
|
+
it 'errors when a parameter is not present' do
|
318
|
+
get '/within_array', children: [
|
319
|
+
{ name: 'Jim', parents: [{}] },
|
320
|
+
{ name: 'Job', parents: [{ name: 'Joy' }] }
|
321
|
+
]
|
322
|
+
# NOTE: with body parameters in json or XML or similar this
|
323
|
+
# should actually fail with: children[parents][name] is missing.
|
324
|
+
last_response.status.should == 400
|
325
|
+
last_response.body.should == 'children[parents] is missing'
|
326
|
+
end
|
327
|
+
|
328
|
+
it 'safely handles empty arrays and blank parameters' do
|
329
|
+
# NOTE: with body parameters in json or XML or similar this
|
330
|
+
# should actually return 200, since an empty array is valid.
|
331
|
+
get '/within_array', children: []
|
332
|
+
last_response.status.should == 400
|
333
|
+
last_response.body.should == 'children is missing'
|
334
|
+
get '/within_array', children: [name: 'Jay']
|
335
|
+
last_response.status.should == 400
|
336
|
+
last_response.body.should == 'children[parents] is missing'
|
337
|
+
end
|
338
|
+
|
339
|
+
it "errors when param is not an Array" do
|
340
|
+
# NOTE: would be nicer if these just returned 'children is invalid'
|
341
|
+
get '/within_array', children: "hello"
|
342
|
+
last_response.status.should == 400
|
343
|
+
last_response.body.should == 'children is invalid, children[name] is missing, children[parents] is missing, children[parents] is invalid, children[parents][name] is missing'
|
344
|
+
|
345
|
+
get '/within_array', children: { name: 'foo' }
|
346
|
+
last_response.status.should == 400
|
347
|
+
last_response.body.should == 'children is invalid, children[parents] is missing'
|
348
|
+
|
349
|
+
get '/within_array', children: [name: 'Jay', parents: { name: 'Fred' }]
|
350
|
+
last_response.status.should == 400
|
351
|
+
last_response.body.should == 'children[parents] is invalid'
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
context 'with block param' do
|
356
|
+
before do
|
357
|
+
subject.params do
|
358
|
+
requires :planets do
|
359
|
+
requires :name
|
360
|
+
end
|
361
|
+
end
|
362
|
+
subject.get '/req' do
|
363
|
+
'within array works'
|
364
|
+
end
|
365
|
+
subject.put '/req' do
|
366
|
+
''
|
367
|
+
end
|
368
|
+
|
369
|
+
subject.params do
|
370
|
+
group :stars do
|
371
|
+
requires :name
|
372
|
+
end
|
373
|
+
end
|
374
|
+
subject.get '/grp' do
|
375
|
+
'within array works'
|
376
|
+
end
|
377
|
+
subject.put '/grp' do
|
378
|
+
''
|
379
|
+
end
|
380
|
+
|
381
|
+
subject.params do
|
382
|
+
requires :name
|
383
|
+
optional :moons do
|
384
|
+
requires :name
|
385
|
+
end
|
386
|
+
end
|
387
|
+
subject.get '/opt' do
|
388
|
+
'within array works'
|
389
|
+
end
|
390
|
+
subject.put '/opt' do
|
391
|
+
''
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
it 'requires defaults to Array type' do
|
396
|
+
get '/req', planets: "Jupiter, Saturn"
|
397
|
+
last_response.status.should == 400
|
398
|
+
last_response.body.should == 'planets is invalid, planets[name] is missing'
|
399
|
+
|
400
|
+
get '/req', planets: { name: 'Jupiter' }
|
401
|
+
last_response.status.should == 400
|
402
|
+
last_response.body.should == 'planets is invalid'
|
403
|
+
|
404
|
+
get '/req', planets: [{ name: 'Venus' }, { name: 'Mars' }]
|
405
|
+
last_response.status.should == 200
|
406
|
+
|
407
|
+
put_with_json '/req', planets: []
|
408
|
+
last_response.status.should == 200
|
409
|
+
end
|
410
|
+
|
411
|
+
it 'optional defaults to Array type' do
|
412
|
+
get '/opt', name: "Jupiter", moons: "Europa, Ganymede"
|
413
|
+
last_response.status.should == 400
|
414
|
+
last_response.body.should == 'moons is invalid, moons[name] is missing'
|
415
|
+
|
416
|
+
get '/opt', name: "Jupiter", moons: { name: 'Ganymede' }
|
417
|
+
last_response.status.should == 400
|
418
|
+
last_response.body.should == 'moons is invalid'
|
419
|
+
|
420
|
+
get '/opt', name: "Jupiter", moons: [{ name: 'Io' }, { name: 'Callisto' }]
|
421
|
+
last_response.status.should == 200
|
422
|
+
|
423
|
+
put_with_json '/opt', name: "Venus"
|
424
|
+
last_response.status.should == 200
|
425
|
+
|
426
|
+
put_with_json '/opt', name: "Mercury", moons: []
|
427
|
+
last_response.status.should == 200
|
428
|
+
end
|
429
|
+
|
430
|
+
it 'group defaults to Array type' do
|
431
|
+
get '/grp', stars: "Sun"
|
432
|
+
last_response.status.should == 400
|
433
|
+
last_response.body.should == 'stars is invalid, stars[name] is missing'
|
434
|
+
|
435
|
+
get '/grp', stars: { name: 'Sun' }
|
436
|
+
last_response.status.should == 400
|
437
|
+
last_response.body.should == 'stars is invalid'
|
438
|
+
|
439
|
+
get '/grp', stars: [{ name: 'Sun' }]
|
440
|
+
last_response.status.should == 200
|
441
|
+
|
442
|
+
put_with_json '/grp', stars: []
|
443
|
+
last_response.status.should == 200
|
444
|
+
end
|
445
|
+
end
|
446
|
+
|
447
|
+
context 'validation within arrays with JSON' do
|
448
|
+
before do
|
449
|
+
subject.params do
|
450
|
+
group :children do
|
451
|
+
requires :name
|
452
|
+
group :parents do
|
453
|
+
requires :name
|
454
|
+
end
|
455
|
+
end
|
456
|
+
end
|
457
|
+
subject.put '/within_array' do
|
458
|
+
'within array works'
|
459
|
+
end
|
460
|
+
end
|
461
|
+
|
462
|
+
it 'can handle new scopes within child elements' do
|
463
|
+
put_with_json '/within_array', children: [
|
464
|
+
{ name: 'John', parents: [{ name: 'Jane' }, { name: 'Bob' }] },
|
465
|
+
{ name: 'Joe', parents: [{ name: 'Josie' }] }
|
466
|
+
]
|
467
|
+
last_response.status.should == 200
|
468
|
+
last_response.body.should == 'within array works'
|
469
|
+
end
|
470
|
+
|
471
|
+
it 'errors when a parameter is not present' do
|
472
|
+
put_with_json '/within_array', children: [
|
473
|
+
{ name: 'Jim', parents: [{}] },
|
474
|
+
{ name: 'Job', parents: [{ name: 'Joy' }] }
|
475
|
+
]
|
476
|
+
last_response.status.should == 400
|
477
|
+
last_response.body.should == 'children[parents][name] is missing'
|
478
|
+
end
|
479
|
+
|
480
|
+
it 'safely handles empty arrays and blank parameters' do
|
481
|
+
put_with_json '/within_array', children: []
|
482
|
+
last_response.status.should == 200
|
483
|
+
put_with_json '/within_array', children: [name: 'Jay']
|
484
|
+
last_response.status.should == 400
|
485
|
+
last_response.body.should == 'children[parents] is missing'
|
486
|
+
end
|
487
|
+
end
|
488
|
+
|
489
|
+
context 'optional with an Array block' do
|
490
|
+
before do
|
491
|
+
subject.params do
|
492
|
+
optional :items, type: Array do
|
493
|
+
requires :key
|
494
|
+
end
|
495
|
+
end
|
496
|
+
subject.get '/optional_group' do
|
497
|
+
'optional group works'
|
498
|
+
end
|
499
|
+
end
|
500
|
+
|
501
|
+
it "doesn't throw a missing param when the group isn't present" do
|
502
|
+
get '/optional_group'
|
503
|
+
last_response.status.should == 200
|
504
|
+
last_response.body.should == 'optional group works'
|
505
|
+
end
|
506
|
+
|
507
|
+
it "doesn't throw a missing param when both group and param are given" do
|
508
|
+
get '/optional_group', items: [{ key: 'foo' }]
|
509
|
+
last_response.status.should == 200
|
510
|
+
last_response.body.should == 'optional group works'
|
511
|
+
end
|
512
|
+
|
513
|
+
it "errors when group is present, but required param is not" do
|
514
|
+
get '/optional_group', items: [{ not_key: 'foo' }]
|
515
|
+
last_response.status.should == 400
|
516
|
+
last_response.body.should == 'items[key] is missing'
|
517
|
+
end
|
518
|
+
|
519
|
+
it "errors when param is present but isn't an Array" do
|
520
|
+
get '/optional_group', items: "hello"
|
521
|
+
last_response.status.should == 400
|
522
|
+
last_response.body.should == 'items is invalid, items[key] is missing'
|
523
|
+
|
524
|
+
get '/optional_group', items: { key: 'foo' }
|
525
|
+
last_response.status.should == 400
|
526
|
+
last_response.body.should == 'items is invalid'
|
527
|
+
end
|
528
|
+
|
529
|
+
it 'adds to declared parameters' do
|
530
|
+
subject.params do
|
531
|
+
optional :items do
|
532
|
+
requires :key
|
533
|
+
end
|
534
|
+
end
|
535
|
+
subject.settings[:declared_params].should == [items: [:key]]
|
536
|
+
end
|
537
|
+
end
|
538
|
+
|
539
|
+
context 'nested optional Array blocks' do
|
540
|
+
before do
|
541
|
+
subject.params do
|
542
|
+
optional :items, type: Array do
|
543
|
+
requires :key
|
544
|
+
optional(:optional_subitems, type: Array) { requires :value }
|
545
|
+
requires(:required_subitems, type: Array) { requires :value }
|
546
|
+
end
|
547
|
+
end
|
548
|
+
subject.get('/nested_optional_group') { 'nested optional group works' }
|
549
|
+
end
|
550
|
+
|
551
|
+
it 'does no internal validations if the outer group is blank' do
|
552
|
+
get '/nested_optional_group'
|
553
|
+
last_response.status.should == 200
|
554
|
+
last_response.body.should == 'nested optional group works'
|
555
|
+
end
|
556
|
+
|
557
|
+
it 'does internal validations if the outer group is present' do
|
558
|
+
get '/nested_optional_group', items: [{ key: 'foo' }]
|
559
|
+
last_response.status.should == 400
|
560
|
+
last_response.body.should == 'items[required_subitems] is missing'
|
561
|
+
|
562
|
+
get '/nested_optional_group', items: [{ key: 'foo', required_subitems: [{ value: 'bar' }] }]
|
563
|
+
last_response.status.should == 200
|
564
|
+
last_response.body.should == 'nested optional group works'
|
565
|
+
end
|
566
|
+
|
567
|
+
it 'handles deep nesting' do
|
568
|
+
get '/nested_optional_group', items: [{ key: 'foo', required_subitems: [{ value: 'bar' }], optional_subitems: [{ not_value: 'baz' }] }]
|
569
|
+
last_response.status.should == 400
|
570
|
+
last_response.body.should == 'items[optional_subitems][value] is missing'
|
571
|
+
|
572
|
+
get '/nested_optional_group', items: [{ key: 'foo', required_subitems: [{ value: 'bar' }], optional_subitems: [{ value: 'baz' }] }]
|
573
|
+
last_response.status.should == 200
|
574
|
+
last_response.body.should == 'nested optional group works'
|
575
|
+
end
|
576
|
+
|
577
|
+
it 'handles validation within arrays' do
|
578
|
+
get '/nested_optional_group', items: [{ key: 'foo' }]
|
579
|
+
last_response.status.should == 400
|
580
|
+
last_response.body.should == 'items[required_subitems] is missing'
|
581
|
+
|
582
|
+
get '/nested_optional_group', items: [{ key: 'foo', required_subitems: [{ value: 'bar' }] }]
|
583
|
+
last_response.status.should == 200
|
584
|
+
last_response.body.should == 'nested optional group works'
|
585
|
+
|
586
|
+
get '/nested_optional_group', items: [{ key: 'foo', required_subitems: [{ value: 'bar' }], optional_subitems: [{ not_value: 'baz' }] }]
|
587
|
+
last_response.status.should == 400
|
588
|
+
last_response.body.should == 'items[optional_subitems][value] is missing'
|
589
|
+
end
|
590
|
+
|
591
|
+
it 'adds to declared parameters' do
|
592
|
+
subject.params do
|
593
|
+
optional :items do
|
594
|
+
requires :key
|
595
|
+
optional(:optional_subitems) { requires :value }
|
596
|
+
requires(:required_subitems) { requires :value }
|
597
|
+
end
|
598
|
+
end
|
599
|
+
subject.settings[:declared_params].should == [items: [:key, { optional_subitems: [:value] }, { required_subitems: [:value] }]]
|
600
|
+
end
|
601
|
+
end
|
602
|
+
|
603
|
+
context 'multiple validation errors' do
|
604
|
+
before do
|
605
|
+
subject.params do
|
606
|
+
requires :yolo
|
607
|
+
requires :swag
|
608
|
+
end
|
609
|
+
subject.get '/two_required' do
|
610
|
+
'two required works'
|
611
|
+
end
|
612
|
+
end
|
613
|
+
|
614
|
+
it 'throws the validation errors' do
|
615
|
+
get '/two_required'
|
616
|
+
last_response.status.should == 400
|
617
|
+
last_response.body.should =~ /yolo is missing/
|
618
|
+
last_response.body.should =~ /swag is missing/
|
619
|
+
end
|
82
620
|
end
|
83
621
|
|
84
622
|
context 'custom validation' do
|
@@ -86,7 +624,7 @@ describe Grape::Validations do
|
|
86
624
|
class Customvalidator < Grape::Validations::Validator
|
87
625
|
def validate_param!(attr_name, params)
|
88
626
|
unless params[attr_name] == 'im custom'
|
89
|
-
|
627
|
+
raise Grape::Exceptions::Validation, param: @scope.full_name(attr_name), message: "is not custom!"
|
90
628
|
end
|
91
629
|
end
|
92
630
|
end
|
@@ -94,18 +632,22 @@ describe Grape::Validations do
|
|
94
632
|
|
95
633
|
context 'when using optional with a custom validator' do
|
96
634
|
before do
|
97
|
-
subject.params
|
98
|
-
|
635
|
+
subject.params do
|
636
|
+
optional :custom, customvalidator: true
|
637
|
+
end
|
638
|
+
subject.get '/optional_custom' do
|
639
|
+
'optional with custom works!'
|
640
|
+
end
|
99
641
|
end
|
100
642
|
|
101
643
|
it 'validates when param is present' do
|
102
|
-
get '/optional_custom',
|
644
|
+
get '/optional_custom', custom: 'im custom'
|
103
645
|
last_response.status.should == 200
|
104
646
|
last_response.body.should == 'optional with custom works!'
|
105
647
|
|
106
|
-
get '/optional_custom',
|
648
|
+
get '/optional_custom', custom: 'im wrong'
|
107
649
|
last_response.status.should == 400
|
108
|
-
last_response.body.should == 'custom
|
650
|
+
last_response.body.should == 'custom is not custom!'
|
109
651
|
end
|
110
652
|
|
111
653
|
it "skips validation when parameter isn't present" do
|
@@ -115,26 +657,32 @@ describe Grape::Validations do
|
|
115
657
|
end
|
116
658
|
|
117
659
|
it 'validates with custom validator when param present and incorrect type' do
|
118
|
-
subject.params
|
660
|
+
subject.params do
|
661
|
+
optional :custom, type: String, customvalidator: true
|
662
|
+
end
|
119
663
|
|
120
|
-
get '/optional_custom',
|
664
|
+
get '/optional_custom', custom: 123
|
121
665
|
last_response.status.should == 400
|
122
|
-
last_response.body.should == 'custom
|
666
|
+
last_response.body.should == 'custom is not custom!'
|
123
667
|
end
|
124
668
|
end
|
125
669
|
|
126
670
|
context 'when using requires with a custom validator' do
|
127
671
|
before do
|
128
|
-
subject.params
|
129
|
-
|
672
|
+
subject.params do
|
673
|
+
requires :custom, customvalidator: true
|
674
|
+
end
|
675
|
+
subject.get '/required_custom' do
|
676
|
+
'required with custom works!'
|
677
|
+
end
|
130
678
|
end
|
131
679
|
|
132
680
|
it 'validates when param is present' do
|
133
|
-
get '/required_custom',
|
681
|
+
get '/required_custom', custom: 'im wrong, validate me'
|
134
682
|
last_response.status.should == 400
|
135
|
-
last_response.body.should == 'custom
|
683
|
+
last_response.body.should == 'custom is not custom!'
|
136
684
|
|
137
|
-
get '/required_custom',
|
685
|
+
get '/required_custom', custom: 'im custom'
|
138
686
|
last_response.status.should == 200
|
139
687
|
last_response.body.should == 'required with custom works!'
|
140
688
|
end
|
@@ -142,55 +690,71 @@ describe Grape::Validations do
|
|
142
690
|
it 'validates when param is not present' do
|
143
691
|
get '/required_custom'
|
144
692
|
last_response.status.should == 400
|
145
|
-
last_response.body.should == 'missing
|
693
|
+
last_response.body.should == 'custom is missing, custom is not custom!'
|
146
694
|
end
|
147
695
|
|
148
696
|
context 'nested namespaces' do
|
149
697
|
before do
|
150
|
-
subject.params
|
698
|
+
subject.params do
|
699
|
+
requires :custom, customvalidator: true
|
700
|
+
end
|
151
701
|
subject.namespace 'nested' do
|
152
|
-
get 'one' do
|
702
|
+
get 'one' do
|
703
|
+
'validation failed'
|
704
|
+
end
|
153
705
|
namespace 'nested' do
|
154
|
-
get 'two' do
|
706
|
+
get 'two' do
|
707
|
+
'validation failed'
|
708
|
+
end
|
155
709
|
end
|
156
710
|
end
|
157
711
|
subject.namespace 'peer' do
|
158
|
-
get 'one' do
|
712
|
+
get 'one' do
|
713
|
+
'no validation required'
|
714
|
+
end
|
159
715
|
namespace 'nested' do
|
160
|
-
get 'two' do
|
716
|
+
get 'two' do
|
717
|
+
'no validation required'
|
718
|
+
end
|
161
719
|
end
|
162
720
|
end
|
163
721
|
|
164
722
|
subject.namespace 'unrelated' do
|
165
|
-
params
|
166
|
-
|
723
|
+
params do
|
724
|
+
requires :name
|
725
|
+
end
|
726
|
+
get 'one' do
|
727
|
+
'validation required'
|
728
|
+
end
|
167
729
|
|
168
730
|
namespace 'double' do
|
169
|
-
get 'two' do
|
731
|
+
get 'two' do
|
732
|
+
'no validation required'
|
733
|
+
end
|
170
734
|
end
|
171
735
|
end
|
172
736
|
end
|
173
737
|
|
174
738
|
specify 'the parent namespace uses the validator' do
|
175
|
-
get '/nested/one',
|
739
|
+
get '/nested/one', custom: 'im wrong, validate me'
|
176
740
|
last_response.status.should == 400
|
177
|
-
last_response.body.should == 'custom
|
741
|
+
last_response.body.should == 'custom is not custom!'
|
178
742
|
end
|
179
743
|
|
180
744
|
specify 'the nested namesapce inherits the custom validator' do
|
181
|
-
get '/nested/nested/two',
|
745
|
+
get '/nested/nested/two', custom: 'im wrong, validate me'
|
182
746
|
last_response.status.should == 400
|
183
|
-
last_response.body.should == 'custom
|
747
|
+
last_response.body.should == 'custom is not custom!'
|
184
748
|
end
|
185
749
|
|
186
750
|
specify 'peer namesapces does not have the validator' do
|
187
|
-
get '/peer/one',
|
751
|
+
get '/peer/one', custom: 'im not validated'
|
188
752
|
last_response.status.should == 200
|
189
753
|
last_response.body.should == 'no validation required'
|
190
754
|
end
|
191
755
|
|
192
756
|
specify 'namespaces nested in peers should also not have the validator' do
|
193
|
-
get '/peer/nested/two',
|
757
|
+
get '/peer/nested/two', custom: 'im not validated'
|
194
758
|
last_response.status.should == 200
|
195
759
|
last_response.body.should == 'no validation required'
|
196
760
|
end
|
@@ -204,5 +768,74 @@ describe Grape::Validations do
|
|
204
768
|
end
|
205
769
|
end
|
206
770
|
end # end custom validation
|
771
|
+
|
772
|
+
context 'named' do
|
773
|
+
context 'can be defined' do
|
774
|
+
it 'in helpers' do
|
775
|
+
subject.helpers do
|
776
|
+
params :pagination do
|
777
|
+
end
|
778
|
+
end
|
779
|
+
end
|
780
|
+
|
781
|
+
it 'in helper module which kind of Grape::API::Helpers' do
|
782
|
+
module SharedParams
|
783
|
+
extend Grape::API::Helpers
|
784
|
+
params :pagination do
|
785
|
+
end
|
786
|
+
end
|
787
|
+
subject.helpers SharedParams
|
788
|
+
end
|
789
|
+
end
|
790
|
+
|
791
|
+
context 'can be included in usual params' do
|
792
|
+
before do
|
793
|
+
module SharedParams
|
794
|
+
extend Grape::API::Helpers
|
795
|
+
params :period do
|
796
|
+
optional :start_date
|
797
|
+
optional :end_date
|
798
|
+
end
|
799
|
+
end
|
800
|
+
subject.helpers SharedParams
|
801
|
+
|
802
|
+
subject.helpers do
|
803
|
+
params :pagination do
|
804
|
+
optional :page, type: Integer
|
805
|
+
optional :per_page, type: Integer
|
806
|
+
end
|
807
|
+
end
|
808
|
+
end
|
809
|
+
|
810
|
+
it 'by #use' do
|
811
|
+
subject.params do
|
812
|
+
use :pagination
|
813
|
+
end
|
814
|
+
subject.settings[:declared_params].should eq [:page, :per_page]
|
815
|
+
end
|
816
|
+
|
817
|
+
it 'by #use with multiple params' do
|
818
|
+
subject.params do
|
819
|
+
use :pagination, :period
|
820
|
+
end
|
821
|
+
subject.settings[:declared_params].should eq [:page, :per_page, :start_date, :end_date]
|
822
|
+
end
|
823
|
+
|
824
|
+
end
|
825
|
+
end
|
826
|
+
|
827
|
+
context 'documentation' do
|
828
|
+
it 'can be included with a hash' do
|
829
|
+
documentation = { example: 'Joe' }
|
830
|
+
|
831
|
+
subject.params do
|
832
|
+
requires 'first_name', documentation: documentation
|
833
|
+
end
|
834
|
+
subject.get '/' do
|
835
|
+
end
|
836
|
+
|
837
|
+
subject.routes.first.route_params['first_name'][:documentation].should eq(documentation)
|
838
|
+
end
|
839
|
+
end
|
207
840
|
end
|
208
841
|
end
|