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.

Files changed (101) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +8 -0
  3. data/.rubocop.yml +70 -0
  4. data/.travis.yml +7 -6
  5. data/CHANGELOG.md +134 -4
  6. data/CONTRIBUTING.md +118 -0
  7. data/Gemfile +5 -2
  8. data/README.md +551 -116
  9. data/RELEASING.md +105 -0
  10. data/Rakefile +29 -8
  11. data/UPGRADING.md +124 -0
  12. data/grape.gemspec +3 -3
  13. data/lib/grape/api.rb +207 -88
  14. data/lib/grape/cookies.rb +4 -8
  15. data/lib/grape/endpoint.rb +198 -144
  16. data/lib/grape/error_formatter/base.rb +5 -7
  17. data/lib/grape/error_formatter/json.rb +3 -5
  18. data/lib/grape/error_formatter/txt.rb +1 -3
  19. data/lib/grape/error_formatter/xml.rb +4 -6
  20. data/lib/grape/exceptions/base.rb +9 -9
  21. data/lib/grape/exceptions/incompatible_option_values.rb +10 -0
  22. data/lib/grape/exceptions/invalid_formatter.rb +1 -4
  23. data/lib/grape/exceptions/invalid_versioner_option.rb +1 -5
  24. data/lib/grape/exceptions/invalid_with_option_for_represent.rb +1 -6
  25. data/lib/grape/exceptions/missing_mime_type.rb +1 -5
  26. data/lib/grape/exceptions/missing_option.rb +1 -4
  27. data/lib/grape/exceptions/missing_vendor_option.rb +1 -4
  28. data/lib/grape/exceptions/unknown_options.rb +1 -5
  29. data/lib/grape/exceptions/unknown_validator.rb +1 -3
  30. data/lib/grape/exceptions/validation.rb +13 -3
  31. data/lib/grape/exceptions/validation_errors.rb +43 -0
  32. data/lib/grape/formatter/base.rb +5 -7
  33. data/lib/grape/formatter/json.rb +0 -3
  34. data/lib/grape/formatter/serializable_hash.rb +15 -15
  35. data/lib/grape/formatter/txt.rb +0 -2
  36. data/lib/grape/formatter/xml.rb +0 -2
  37. data/lib/grape/http/request.rb +26 -0
  38. data/lib/grape/locale/en.yml +8 -5
  39. data/lib/grape/middleware/auth/base.rb +30 -0
  40. data/lib/grape/middleware/auth/basic.rb +3 -20
  41. data/lib/grape/middleware/auth/digest.rb +2 -19
  42. data/lib/grape/middleware/auth/oauth2.rb +31 -24
  43. data/lib/grape/middleware/base.rb +7 -7
  44. data/lib/grape/middleware/error.rb +36 -22
  45. data/lib/grape/middleware/filter.rb +3 -3
  46. data/lib/grape/middleware/formatter.rb +99 -61
  47. data/lib/grape/middleware/globals.rb +13 -0
  48. data/lib/grape/middleware/versioner/accept_version_header.rb +67 -0
  49. data/lib/grape/middleware/versioner/header.rb +22 -16
  50. data/lib/grape/middleware/versioner/param.rb +9 -11
  51. data/lib/grape/middleware/versioner/path.rb +10 -13
  52. data/lib/grape/middleware/versioner.rb +3 -1
  53. data/lib/grape/namespace.rb +23 -0
  54. data/lib/grape/parser/base.rb +3 -5
  55. data/lib/grape/parser/json.rb +0 -2
  56. data/lib/grape/parser/xml.rb +0 -2
  57. data/lib/grape/path.rb +70 -0
  58. data/lib/grape/route.rb +10 -6
  59. data/lib/grape/util/content_types.rb +2 -1
  60. data/lib/grape/util/deep_merge.rb +5 -5
  61. data/lib/grape/util/hash_stack.rb +13 -2
  62. data/lib/grape/validations/coerce.rb +11 -10
  63. data/lib/grape/validations/default.rb +25 -0
  64. data/lib/grape/validations/presence.rb +7 -3
  65. data/lib/grape/validations/regexp.rb +2 -5
  66. data/lib/grape/validations/values.rb +17 -0
  67. data/lib/grape/validations.rb +161 -54
  68. data/lib/grape/version.rb +1 -1
  69. data/lib/grape.rb +19 -4
  70. data/spec/grape/api_spec.rb +897 -268
  71. data/spec/grape/endpoint_spec.rb +283 -66
  72. data/spec/grape/entity_spec.rb +132 -29
  73. data/spec/grape/exceptions/missing_mime_type_spec.rb +3 -9
  74. data/spec/grape/exceptions/validation_errors_spec.rb +19 -0
  75. data/spec/grape/middleware/auth/basic_spec.rb +8 -8
  76. data/spec/grape/middleware/auth/digest_spec.rb +5 -5
  77. data/spec/grape/middleware/auth/oauth2_spec.rb +81 -36
  78. data/spec/grape/middleware/base_spec.rb +8 -13
  79. data/spec/grape/middleware/error_spec.rb +13 -17
  80. data/spec/grape/middleware/exception_spec.rb +47 -27
  81. data/spec/grape/middleware/formatter_spec.rb +103 -41
  82. data/spec/grape/middleware/versioner/accept_version_header_spec.rb +121 -0
  83. data/spec/grape/middleware/versioner/header_spec.rb +76 -51
  84. data/spec/grape/middleware/versioner/param_spec.rb +18 -18
  85. data/spec/grape/middleware/versioner/path_spec.rb +6 -6
  86. data/spec/grape/middleware/versioner_spec.rb +5 -2
  87. data/spec/grape/path_spec.rb +229 -0
  88. data/spec/grape/util/hash_stack_spec.rb +31 -32
  89. data/spec/grape/validations/coerce_spec.rb +116 -51
  90. data/spec/grape/validations/default_spec.rb +123 -0
  91. data/spec/grape/validations/presence_spec.rb +42 -44
  92. data/spec/grape/validations/regexp_spec.rb +9 -9
  93. data/spec/grape/validations/values_spec.rb +138 -0
  94. data/spec/grape/validations/zh-CN.yml +4 -3
  95. data/spec/grape/validations_spec.rb +681 -48
  96. data/spec/shared/versioning_examples.rb +22 -6
  97. data/spec/spec_helper.rb +3 -2
  98. data/spec/support/basic_auth_encode_helpers.rb +0 -1
  99. data/spec/support/content_type_helpers.rb +11 -0
  100. data/spec/support/versioned_helpers.rb +13 -5
  101. 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
- def app; subject end
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 { optional :a_number, :regexp => /^[0-9]+$/ }
11
- subject.get '/optional' do 'optional works!'; end
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', { :a_number => 'string' }
21
+ get '/optional', a_number: 'string'
14
22
  last_response.status.should == 400
15
- last_response.body.should == 'invalid parameter: a_number'
23
+ last_response.body.should == 'a_number is invalid'
16
24
 
17
- get '/optional', { :a_number => 45 }
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 { optional :a_number, :regexp => /^[0-9]+$/ }
24
- subject.get '/optional' do 'optional works!'; end
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 { optional :some_param }
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 { requires :key }
40
- subject.get '/required' do 'required works'; end
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 == 'missing parameter: key'
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', { :key => 'cool' }
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 { requires :some_param }
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 'required works'; end
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 == 'missing parameter: items[key]'
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', { :items => [:key => 'hello', :key => 'world'] }
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
- throw :error, :status => 400, :message => "#{attr_name}: is not custom!"
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 { optional :custom, :customvalidator => true }
98
- subject.get '/optional_custom' do 'optional with custom works!'; end
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', { :custom => 'im 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', { :custom => 'im wrong' }
648
+ get '/optional_custom', custom: 'im wrong'
107
649
  last_response.status.should == 400
108
- last_response.body.should == 'custom: is not 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 { optional :custom, :type => String, :customvalidator => true }
660
+ subject.params do
661
+ optional :custom, type: String, customvalidator: true
662
+ end
119
663
 
120
- get '/optional_custom', { :custom => 123 }
664
+ get '/optional_custom', custom: 123
121
665
  last_response.status.should == 400
122
- last_response.body.should == 'custom: is not 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 { requires :custom, :customvalidator => true }
129
- subject.get '/required_custom' do 'required with custom works!'; end
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', { :custom => 'im wrong, validate me' }
681
+ get '/required_custom', custom: 'im wrong, validate me'
134
682
  last_response.status.should == 400
135
- last_response.body.should == 'custom: is not custom!'
683
+ last_response.body.should == 'custom is not custom!'
136
684
 
137
- get '/required_custom', { :custom => 'im 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 parameter: custom'
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 { requires :custom, :customvalidator => true }
698
+ subject.params do
699
+ requires :custom, customvalidator: true
700
+ end
151
701
  subject.namespace 'nested' do
152
- get 'one' do 'validation failed' end
702
+ get 'one' do
703
+ 'validation failed'
704
+ end
153
705
  namespace 'nested' do
154
- get 'two' do 'validation failed' end
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 'no validation required' end
712
+ get 'one' do
713
+ 'no validation required'
714
+ end
159
715
  namespace 'nested' do
160
- get 'two' do 'no validation required' end
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{ requires :name }
166
- get 'one' do 'validation required'; end
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 'no validation required' end
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', { :custom => 'im wrong, validate me'}
739
+ get '/nested/one', custom: 'im wrong, validate me'
176
740
  last_response.status.should == 400
177
- last_response.body.should == 'custom: is not 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', { :custom => 'im wrong, validate me'}
745
+ get '/nested/nested/two', custom: 'im wrong, validate me'
182
746
  last_response.status.should == 400
183
- last_response.body.should == 'custom: is not 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', { :custom => 'im not validated' }
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', { :custom => 'im not validated' }
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