grape 0.12.0 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of grape might be problematic. Click here for more details.

Files changed (123) hide show
  1. checksums.yaml +4 -4
  2. data/Appraisals +9 -4
  3. data/CHANGELOG.md +265 -215
  4. data/CONTRIBUTING.md +4 -4
  5. data/Gemfile +0 -1
  6. data/Gemfile.lock +166 -0
  7. data/README.md +426 -161
  8. data/RELEASING.md +14 -6
  9. data/Rakefile +30 -33
  10. data/UPGRADING.md +54 -23
  11. data/benchmark/simple.rb +27 -0
  12. data/gemfiles/rack_1.5.2.gemfile +13 -0
  13. data/gemfiles/rails_3.gemfile +2 -2
  14. data/gemfiles/rails_4.gemfile +1 -2
  15. data/grape.gemspec +6 -7
  16. data/lib/grape/api.rb +24 -4
  17. data/lib/grape/dsl/callbacks.rb +20 -0
  18. data/lib/grape/dsl/configuration.rb +59 -2
  19. data/lib/grape/dsl/helpers.rb +8 -3
  20. data/lib/grape/dsl/inside_route.rb +100 -45
  21. data/lib/grape/dsl/parameters.rb +96 -7
  22. data/lib/grape/dsl/request_response.rb +1 -1
  23. data/lib/grape/dsl/routing.rb +17 -4
  24. data/lib/grape/dsl/settings.rb +36 -1
  25. data/lib/grape/dsl/validations.rb +7 -5
  26. data/lib/grape/endpoint.rb +102 -57
  27. data/lib/grape/error_formatter/base.rb +6 -6
  28. data/lib/grape/exceptions/base.rb +5 -5
  29. data/lib/grape/exceptions/invalid_version_header.rb +10 -0
  30. data/lib/grape/exceptions/unknown_parameter.rb +10 -0
  31. data/lib/grape/exceptions/validation_errors.rb +4 -3
  32. data/lib/grape/formatter/serializable_hash.rb +3 -2
  33. data/lib/grape/http/headers.rb +0 -1
  34. data/lib/grape/locale/en.yml +5 -1
  35. data/lib/grape/middleware/auth/base.rb +2 -2
  36. data/lib/grape/middleware/auth/dsl.rb +1 -1
  37. data/lib/grape/middleware/auth/strategies.rb +1 -1
  38. data/lib/grape/middleware/base.rb +8 -4
  39. data/lib/grape/middleware/error.rb +3 -2
  40. data/lib/grape/middleware/filter.rb +1 -1
  41. data/lib/grape/middleware/formatter.rb +64 -45
  42. data/lib/grape/middleware/globals.rb +3 -3
  43. data/lib/grape/middleware/versioner/accept_version_header.rb +5 -7
  44. data/lib/grape/middleware/versioner/header.rb +113 -50
  45. data/lib/grape/middleware/versioner/param.rb +5 -8
  46. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +20 -0
  47. data/lib/grape/middleware/versioner/path.rb +3 -6
  48. data/lib/grape/namespace.rb +13 -2
  49. data/lib/grape/path.rb +4 -3
  50. data/lib/grape/request.rb +40 -0
  51. data/lib/grape/route.rb +5 -0
  52. data/lib/grape/util/content_types.rb +9 -9
  53. data/lib/grape/util/env.rb +22 -0
  54. data/lib/grape/util/file_response.rb +21 -0
  55. data/lib/grape/util/inheritable_setting.rb +23 -2
  56. data/lib/grape/util/inheritable_values.rb +1 -1
  57. data/lib/grape/util/stackable_values.rb +5 -2
  58. data/lib/grape/util/strict_hash_configuration.rb +2 -1
  59. data/lib/grape/validations/attributes_iterator.rb +8 -3
  60. data/lib/grape/validations/params_scope.rb +164 -22
  61. data/lib/grape/validations/types/build_coercer.rb +53 -0
  62. data/lib/grape/validations/types/custom_type_coercer.rb +183 -0
  63. data/lib/grape/validations/types/file.rb +28 -0
  64. data/lib/grape/validations/types/json.rb +65 -0
  65. data/lib/grape/validations/types/multiple_type_coercer.rb +76 -0
  66. data/lib/grape/validations/types/variant_collection_coercer.rb +59 -0
  67. data/lib/grape/validations/types/virtus_collection_patch.rb +16 -0
  68. data/lib/grape/validations/types.rb +144 -0
  69. data/lib/grape/validations/validators/all_or_none.rb +1 -1
  70. data/lib/grape/validations/validators/allow_blank.rb +3 -3
  71. data/lib/grape/validations/validators/base.rb +7 -0
  72. data/lib/grape/validations/validators/coerce.rb +32 -34
  73. data/lib/grape/validations/validators/presence.rb +2 -3
  74. data/lib/grape/validations/validators/regexp.rb +2 -4
  75. data/lib/grape/validations/validators/values.rb +3 -3
  76. data/lib/grape/validations.rb +5 -0
  77. data/lib/grape/version.rb +2 -1
  78. data/lib/grape.rb +15 -12
  79. data/pkg/grape-0.13.0.gem +0 -0
  80. data/spec/grape/api/custom_validations_spec.rb +5 -4
  81. data/spec/grape/api/deeply_included_options_spec.rb +7 -7
  82. data/spec/grape/api/nested_helpers_spec.rb +4 -2
  83. data/spec/grape/api/shared_helpers_spec.rb +8 -8
  84. data/spec/grape/api_spec.rb +151 -54
  85. data/spec/grape/dsl/configuration_spec.rb +13 -0
  86. data/spec/grape/dsl/helpers_spec.rb +16 -2
  87. data/spec/grape/dsl/inside_route_spec.rb +40 -4
  88. data/spec/grape/dsl/parameters_spec.rb +0 -6
  89. data/spec/grape/dsl/routing_spec.rb +1 -1
  90. data/spec/grape/dsl/validations_spec.rb +18 -0
  91. data/spec/grape/endpoint_spec.rb +130 -6
  92. data/spec/grape/entity_spec.rb +10 -8
  93. data/spec/grape/exceptions/invalid_accept_header_spec.rb +1 -15
  94. data/spec/grape/exceptions/validation_errors_spec.rb +28 -0
  95. data/spec/grape/integration/rack_spec.rb +3 -2
  96. data/spec/grape/middleware/base_spec.rb +40 -16
  97. data/spec/grape/middleware/error_spec.rb +16 -15
  98. data/spec/grape/middleware/exception_spec.rb +45 -43
  99. data/spec/grape/middleware/formatter_spec.rb +34 -5
  100. data/spec/grape/middleware/versioner/header_spec.rb +79 -47
  101. data/spec/grape/path_spec.rb +10 -10
  102. data/spec/grape/presenters/presenter_spec.rb +2 -2
  103. data/spec/grape/request_spec.rb +100 -0
  104. data/spec/grape/util/inheritable_values_spec.rb +14 -0
  105. data/spec/grape/util/stackable_values_spec.rb +10 -0
  106. data/spec/grape/validations/params_scope_spec.rb +86 -0
  107. data/spec/grape/validations/types_spec.rb +95 -0
  108. data/spec/grape/validations/validators/coerce_spec.rb +364 -10
  109. data/spec/grape/validations/validators/values_spec.rb +27 -15
  110. data/spec/grape/validations_spec.rb +53 -24
  111. data/spec/shared/versioning_examples.rb +2 -2
  112. data/spec/spec_helper.rb +0 -1
  113. data/spec/support/versioned_helpers.rb +2 -2
  114. metadata +55 -14
  115. data/.gitignore +0 -46
  116. data/.rspec +0 -2
  117. data/.rubocop.yml +0 -7
  118. data/.rubocop_todo.yml +0 -84
  119. data/.travis.yml +0 -20
  120. data/.yardopts +0 -2
  121. data/lib/backports/active_support/deep_dup.rb +0 -49
  122. data/lib/backports/active_support/duplicable.rb +0 -88
  123. data/lib/grape/http/request.rb +0 -27
@@ -0,0 +1,95 @@
1
+ require 'spec_helper'
2
+
3
+ describe Grape::Validations::Types do
4
+ module TypesSpec
5
+ class FooType
6
+ def self.parse(_)
7
+ end
8
+ end
9
+
10
+ class BarType
11
+ def self.parse
12
+ end
13
+ end
14
+ end
15
+
16
+ VirtusA = Virtus::Attribute.build(String)
17
+
18
+ module VirtusModule
19
+ include Virtus.module
20
+ end
21
+
22
+ class VirtusB
23
+ include VirtusModule
24
+ end
25
+
26
+ class VirtusC
27
+ include Virtus.model
28
+ end
29
+
30
+ MyAxiom = Axiom::Types::String.new do
31
+ minimum_length 1
32
+ maximum_length 30
33
+ end
34
+
35
+ describe '::primitive?' do
36
+ [
37
+ Integer, Float, Numeric, BigDecimal,
38
+ Virtus::Attribute::Boolean, String, Symbol,
39
+ Date, DateTime, Time, Rack::Multipart::UploadedFile
40
+ ].each do |type|
41
+ it "recognizes #{type} as a primitive" do
42
+ expect(described_class.primitive?(type)).to be_truthy
43
+ end
44
+ end
45
+
46
+ it 'identifies unknown types' do
47
+ expect(described_class.primitive?(Object)).to be_falsy
48
+ expect(described_class.primitive?(TypesSpec::FooType)).to be_falsy
49
+ end
50
+ end
51
+
52
+ describe '::structure?' do
53
+ [
54
+ Hash, Array, Set
55
+ ].each do |type|
56
+ it "recognizes #{type} as a structure" do
57
+ expect(described_class.structure?(type)).to be_truthy
58
+ end
59
+ end
60
+ end
61
+
62
+ describe '::recognized?' do
63
+ [
64
+ VirtusA, VirtusB, VirtusC, MyAxiom
65
+ ].each do |type|
66
+ it "recognizes #{type}" do
67
+ expect(described_class.recognized?(type)).to be_truthy
68
+ end
69
+ end
70
+ end
71
+
72
+ describe '::special?' do
73
+ [
74
+ JSON, Array[JSON], File, Rack::Multipart::UploadedFile
75
+ ].each do |type|
76
+ it "provides special handling for #{type.inspect}" do
77
+ expect(described_class.special?(type)).to be_truthy
78
+ end
79
+ end
80
+ end
81
+
82
+ describe '::custom?' do
83
+ it 'returns false if the type does not respond to :parse' do
84
+ expect(described_class.custom?(Object)).to be_falsy
85
+ end
86
+
87
+ it 'returns true if the type responds to :parse with one argument' do
88
+ expect(described_class.custom?(TypesSpec::FooType)).to be_truthy
89
+ end
90
+
91
+ it 'returns false if the type\'s #parse method takes other than one argument' do
92
+ expect(described_class.custom?(TypesSpec::BarType)).to be_falsy
93
+ end
94
+ end
95
+ end
@@ -11,6 +11,14 @@ describe Grape::Validations::CoerceValidator do
11
11
  end
12
12
 
13
13
  describe 'coerce' do
14
+ module CoerceValidatorSpec
15
+ class User
16
+ include Virtus.model
17
+ attribute :id, Integer
18
+ attribute :name, String
19
+ end
20
+ end
21
+
14
22
  context 'i18n' do
15
23
  after :each do
16
24
  I18n.locale = :en
@@ -82,14 +90,6 @@ describe Grape::Validations::CoerceValidator do
82
90
  end
83
91
 
84
92
  context 'complex objects' do
85
- module CoerceValidatorSpec
86
- class User
87
- include Virtus.model
88
- attribute :id, Integer
89
- attribute :name, String
90
- end
91
- end
92
-
93
93
  it 'error on malformed input for complex objects' do
94
94
  subject.params do
95
95
  requires :user, type: CoerceValidatorSpec::User
@@ -148,6 +148,27 @@ describe Grape::Validations::CoerceValidator do
148
148
  expect(last_response.status).to eq(200)
149
149
  expect(last_response.body).to eq('TrueClass')
150
150
  end
151
+
152
+ it 'Array of Complex' do
153
+ subject.params do
154
+ requires :arry, coerce: Array[CoerceValidatorSpec::User]
155
+ end
156
+ subject.get '/array' do
157
+ params[:arry].size
158
+ end
159
+
160
+ get 'array', arry: [31]
161
+ expect(last_response.status).to eq(400)
162
+ expect(last_response.body).to eq('arry is invalid')
163
+
164
+ get 'array', arry: { id: 31, name: 'Alice' }
165
+ expect(last_response.status).to eq(400)
166
+ expect(last_response.body).to eq('arry is invalid')
167
+
168
+ get 'array', arry: [{ id: 31, name: 'Alice' }]
169
+ expect(last_response.status).to eq(200)
170
+ expect(last_response.body).to eq('1')
171
+ end
151
172
  end
152
173
 
153
174
  context 'Set' do
@@ -203,9 +224,9 @@ describe Grape::Validations::CoerceValidator do
203
224
  expect(last_response.body).to eq('TrueClass')
204
225
  end
205
226
 
206
- it 'file' do
227
+ it 'Rack::Multipart::UploadedFile' do
207
228
  subject.params do
208
- requires :file, coerce: Rack::Multipart::UploadedFile
229
+ requires :file, type: Rack::Multipart::UploadedFile
209
230
  end
210
231
  subject.post '/upload' do
211
232
  params[:file].filename
@@ -214,6 +235,27 @@ describe Grape::Validations::CoerceValidator do
214
235
  post '/upload', file: Rack::Test::UploadedFile.new(__FILE__)
215
236
  expect(last_response.status).to eq(201)
216
237
  expect(last_response.body).to eq(File.basename(__FILE__).to_s)
238
+
239
+ post '/upload', file: 'not a file'
240
+ expect(last_response.status).to eq(400)
241
+ expect(last_response.body).to eq('file is invalid')
242
+ end
243
+
244
+ it 'File' do
245
+ subject.params do
246
+ requires :file, coerce: File
247
+ end
248
+ subject.post '/upload' do
249
+ params[:file].filename
250
+ end
251
+
252
+ post '/upload', file: Rack::Test::UploadedFile.new(__FILE__)
253
+ expect(last_response.status).to eq(201)
254
+ expect(last_response.body).to eq(File.basename(__FILE__).to_s)
255
+
256
+ post '/upload', file: 'not a file'
257
+ expect(last_response.status).to eq(400)
258
+ expect(last_response.body).to eq('file is invalid')
217
259
  end
218
260
 
219
261
  it 'Nests integers' do
@@ -231,5 +273,317 @@ describe Grape::Validations::CoerceValidator do
231
273
  expect(last_response.body).to eq('Fixnum')
232
274
  end
233
275
  end
276
+
277
+ context 'using coerce_with' do
278
+ it 'uses parse where available' do
279
+ subject.params do
280
+ requires :ints, type: Array, coerce_with: JSON do
281
+ requires :i, type: Integer
282
+ requires :j
283
+ end
284
+ end
285
+ subject.get '/ints' do
286
+ ints = params[:ints].first
287
+ 'coercion works' if ints[:i] == 1 && ints[:j] == '2'
288
+ end
289
+
290
+ get '/ints', ints: [{ i: 1, j: '2' }]
291
+ expect(last_response.status).to eq(400)
292
+ expect(last_response.body).to eq('ints is invalid')
293
+
294
+ get '/ints', ints: '{"i":1,"j":"2"}'
295
+ expect(last_response.status).to eq(400)
296
+ expect(last_response.body).to eq('ints[0][i] is missing, ints[0][i] is invalid, ints[0][j] is missing')
297
+
298
+ get '/ints', ints: '[{"i":"1","j":"2"}]'
299
+ expect(last_response.status).to eq(200)
300
+ expect(last_response.body).to eq('coercion works')
301
+ end
302
+
303
+ it 'accepts any callable' do
304
+ subject.params do
305
+ requires :ints, type: Hash, coerce_with: JSON.method(:parse) do
306
+ requires :int, type: Integer, coerce_with: ->(val) { val == 'three' ? 3 : val }
307
+ end
308
+ end
309
+ subject.get '/ints' do
310
+ params[:ints][:int]
311
+ end
312
+
313
+ get '/ints', ints: '{"int":"3"}'
314
+ expect(last_response.status).to eq(400)
315
+ expect(last_response.body).to eq('ints[int] is invalid')
316
+
317
+ get '/ints', ints: '{"int":"three"}'
318
+ expect(last_response.status).to eq(200)
319
+ expect(last_response.body).to eq('3')
320
+
321
+ get '/ints', ints: '{"int":3}'
322
+ expect(last_response.status).to eq(200)
323
+ expect(last_response.body).to eq('3')
324
+ end
325
+
326
+ it 'must be supplied with :type or :coerce' do
327
+ expect do
328
+ subject.params do
329
+ requires :ints, coerce_with: JSON
330
+ end
331
+ end.to raise_error(ArgumentError)
332
+ end
333
+ end
334
+
335
+ context 'first-class JSON' do
336
+ it 'parses objects and arrays' do
337
+ subject.params do
338
+ requires :splines, type: JSON do
339
+ requires :x, type: Integer, values: [1, 2, 3]
340
+ optional :ints, type: Array[Integer]
341
+ optional :obj, type: Hash do
342
+ optional :y
343
+ end
344
+ end
345
+ end
346
+ subject.get '/' do
347
+ if params[:splines].is_a? Hash
348
+ params[:splines][:obj][:y]
349
+ else
350
+ 'arrays work' if params[:splines].any? { |s| s.key? :obj }
351
+ end
352
+ end
353
+
354
+ get '/', splines: '{"x":1,"ints":[1,2,3],"obj":{"y":"woof"}}'
355
+ expect(last_response.status).to eq(200)
356
+ expect(last_response.body).to eq('woof')
357
+
358
+ get '/', splines: '[{"x":2,"ints":[]},{"x":3,"ints":[4],"obj":{"y":"quack"}}]'
359
+ expect(last_response.status).to eq(200)
360
+ expect(last_response.body).to eq('arrays work')
361
+
362
+ get '/', splines: '{"x":4,"ints":[2]}'
363
+ expect(last_response.status).to eq(400)
364
+ expect(last_response.body).to eq('splines[x] does not have a valid value')
365
+
366
+ get '/', splines: '[{"x":1,"ints":[]},{"x":4,"ints":[]}]'
367
+ expect(last_response.status).to eq(400)
368
+ expect(last_response.body).to eq('splines[x] does not have a valid value')
369
+ end
370
+
371
+ it 'accepts Array[JSON] shorthand' do
372
+ subject.params do
373
+ requires :splines, type: Array[JSON] do
374
+ requires :x, type: Integer, values: [1, 2, 3]
375
+ requires :y
376
+ end
377
+ end
378
+ subject.get '/' do
379
+ params[:splines].first[:y].class.to_s
380
+ spline = params[:splines].first
381
+ "#{spline[:x].class}.#{spline[:y].class}"
382
+ end
383
+
384
+ get '/', splines: '{"x":"1","y":"woof"}'
385
+ expect(last_response.status).to eq(200)
386
+ expect(last_response.body).to eq('Fixnum.String')
387
+
388
+ get '/', splines: '[{"x":1,"y":2},{"x":1,"y":"quack"}]'
389
+ expect(last_response.status).to eq(200)
390
+ expect(last_response.body).to eq('Fixnum.Fixnum')
391
+
392
+ get '/', splines: '{"x":"4","y":"woof"}'
393
+ expect(last_response.status).to eq(400)
394
+ expect(last_response.body).to eq('splines[x] does not have a valid value')
395
+
396
+ get '/', splines: '[{"x":"4","y":"woof"}]'
397
+ expect(last_response.status).to eq(400)
398
+ expect(last_response.body).to eq('splines[x] does not have a valid value')
399
+ end
400
+
401
+ it "doesn't make sense using coerce_with" do
402
+ expect do
403
+ subject.params do
404
+ requires :bad, type: JSON, coerce_with: JSON do
405
+ requires :x
406
+ end
407
+ end
408
+ end.to raise_error(ArgumentError)
409
+
410
+ expect do
411
+ subject.params do
412
+ requires :bad, type: Array[JSON], coerce_with: JSON do
413
+ requires :x
414
+ end
415
+ end
416
+ end.to raise_error(ArgumentError)
417
+ end
418
+ end
419
+
420
+ context 'multiple types' do
421
+ Boolean = Grape::API::Boolean
422
+
423
+ it 'coerces to first possible type' do
424
+ subject.params do
425
+ requires :a, types: [Boolean, Integer, String]
426
+ end
427
+ subject.get '/' do
428
+ params[:a].class.to_s
429
+ end
430
+
431
+ get '/', a: 'true'
432
+ expect(last_response.status).to eq(200)
433
+ expect(last_response.body).to eq('TrueClass')
434
+
435
+ get '/', a: '5'
436
+ expect(last_response.status).to eq(200)
437
+ expect(last_response.body).to eq('Fixnum')
438
+
439
+ get '/', a: 'anything else'
440
+ expect(last_response.status).to eq(200)
441
+ expect(last_response.body).to eq('String')
442
+ end
443
+
444
+ it 'fails when no coercion is possible' do
445
+ subject.params do
446
+ requires :a, types: [Boolean, Integer]
447
+ end
448
+ subject.get '/' do
449
+ params[:a].class.to_s
450
+ end
451
+
452
+ get '/', a: true
453
+ expect(last_response.status).to eq(200)
454
+ expect(last_response.body).to eq('TrueClass')
455
+
456
+ get '/', a: 'not good'
457
+ expect(last_response.status).to eq(400)
458
+ expect(last_response.body).to eq('a is invalid')
459
+ end
460
+
461
+ context 'for primitive collections' do
462
+ before do
463
+ subject.params do
464
+ optional :a, types: [String, Array[String]]
465
+ optional :b, types: [Array[Integer], Array[String]]
466
+ optional :c, type: Array[Integer, String]
467
+ optional :d, types: [Integer, String, Set[Integer, String]]
468
+ end
469
+ subject.get '/' do
470
+ (
471
+ params[:a] ||
472
+ params[:b] ||
473
+ params[:c] ||
474
+ params[:d]
475
+ ).inspect
476
+ end
477
+ end
478
+
479
+ it 'allows singular form declaration' do
480
+ get '/', a: 'one way'
481
+ expect(last_response.status).to eq(200)
482
+ expect(last_response.body).to eq('"one way"')
483
+
484
+ get '/', a: %w(the other)
485
+ expect(last_response.status).to eq(200)
486
+ expect(last_response.body).to eq('["the", "other"]')
487
+
488
+ get '/', a: { a: 1, b: 2 }
489
+ expect(last_response.status).to eq(400)
490
+ expect(last_response.body).to eq('a is invalid')
491
+
492
+ get '/', a: [1, 2, 3]
493
+ expect(last_response.status).to eq(200)
494
+ expect(last_response.body).to eq('["1", "2", "3"]')
495
+ end
496
+
497
+ it 'allows multiple collection types' do
498
+ get '/', b: [1, 2, 3]
499
+ expect(last_response.status).to eq(200)
500
+ expect(last_response.body).to eq('[1, 2, 3]')
501
+
502
+ get '/', b: %w(1 2 3)
503
+ expect(last_response.status).to eq(200)
504
+ expect(last_response.body).to eq('[1, 2, 3]')
505
+
506
+ get '/', b: [1, true, 'three']
507
+ expect(last_response.status).to eq(200)
508
+ expect(last_response.body).to eq('["1", "true", "three"]')
509
+ end
510
+
511
+ it 'allows collections with multiple types' do
512
+ get '/', c: [1, '2', true, 'three']
513
+ expect(last_response.status).to eq(200)
514
+ expect(last_response.body).to eq('[1, 2, "true", "three"]')
515
+
516
+ get '/', d: '1'
517
+ expect(last_response.status).to eq(200)
518
+ expect(last_response.body).to eq('1')
519
+
520
+ get '/', d: 'one'
521
+ expect(last_response.status).to eq(200)
522
+ expect(last_response.body).to eq('"one"')
523
+
524
+ get '/', d: %w(1 two)
525
+ expect(last_response.status).to eq(200)
526
+ expect(last_response.body).to eq('#<Set: {1, "two"}>')
527
+ end
528
+ end
529
+
530
+ context 'custom coercion rules' do
531
+ before do
532
+ subject.params do
533
+ requires :a, types: [Boolean, String], coerce_with: (lambda do |val|
534
+ if val == 'yup'
535
+ true
536
+ elsif val == 'false'
537
+ 0
538
+ else
539
+ val
540
+ end
541
+ end)
542
+ end
543
+ subject.get '/' do
544
+ params[:a].class.to_s
545
+ end
546
+ end
547
+
548
+ it 'respects :coerce_with' do
549
+ get '/', a: 'yup'
550
+ expect(last_response.status).to eq(200)
551
+ expect(last_response.body).to eq('TrueClass')
552
+ end
553
+
554
+ it 'still validates type' do
555
+ get '/', a: 'false'
556
+ expect(last_response.status).to eq(400)
557
+ expect(last_response.body).to eq('a is invalid')
558
+ end
559
+
560
+ it 'performs no additional coercion' do
561
+ get '/', a: 'true'
562
+ expect(last_response.status).to eq(200)
563
+ expect(last_response.body).to eq('String')
564
+ end
565
+ end
566
+
567
+ it 'may not be supplied together with a single type' do
568
+ expect do
569
+ subject.params do
570
+ requires :a, type: Integer, types: [Integer, String]
571
+ end
572
+ end.to raise_exception ArgumentError
573
+ end
574
+ end
575
+
576
+ context 'converter' do
577
+ it 'does not build Virtus::Attribute multiple times' do
578
+ subject.params do
579
+ requires :something, type: Array[String]
580
+ end
581
+ subject.get do
582
+ end
583
+
584
+ expect(Virtus::Attribute).to receive(:build).at_most(2).times.and_call_original
585
+ 10.times { get '/' }
586
+ end
587
+ end
234
588
  end
235
589
  end
@@ -1,22 +1,22 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Grape::Validations::ValuesValidator do
4
- class ValuesModel
5
- DEFAULT_VALUES = ['valid-type1', 'valid-type2', 'valid-type3']
6
- class << self
7
- def values
8
- @values ||= []
9
- [DEFAULT_VALUES + @values].flatten.uniq
10
- end
4
+ module ValidationsSpec
5
+ class ValuesModel
6
+ DEFAULT_VALUES = ['valid-type1', 'valid-type2', 'valid-type3']
7
+ class << self
8
+ def values
9
+ @values ||= []
10
+ [DEFAULT_VALUES + @values].flatten.uniq
11
+ end
11
12
 
12
- def add_value(value)
13
- @values ||= []
14
- @values << value
13
+ def add_value(value)
14
+ @values ||= []
15
+ @values << value
16
+ end
15
17
  end
16
18
  end
17
- end
18
19
 
19
- module ValidationsSpec
20
20
  module ValuesValidatorSpec
21
21
  class API < Grape::API
22
22
  default_format :json
@@ -56,6 +56,13 @@ describe Grape::Validations::ValuesValidator do
56
56
  { type: params[:type] }
57
57
  end
58
58
 
59
+ params do
60
+ optional :type, type: Boolean, desc: 'A boolean', values: [true]
61
+ end
62
+ get '/values/optional_boolean' do
63
+ { type: params[:type] }
64
+ end
65
+
59
66
  params do
60
67
  requires :type, type: Integer, desc: 'An integer', values: [10, 11], default: 10
61
68
  end
@@ -122,7 +129,7 @@ describe Grape::Validations::ValuesValidator do
122
129
  end
123
130
 
124
131
  it 'does not validate updated values without proc' do
125
- ValuesModel.add_value('valid-type4')
132
+ ValidationsSpec::ValuesModel.add_value('valid-type4')
126
133
 
127
134
  get('/', type: 'valid-type4')
128
135
  expect(last_response.status).to eq 400
@@ -130,7 +137,7 @@ describe Grape::Validations::ValuesValidator do
130
137
  end
131
138
 
132
139
  it 'validates against values in a proc' do
133
- ValuesModel.add_value('valid-type4')
140
+ ValidationsSpec::ValuesModel.add_value('valid-type4')
134
141
 
135
142
  get('/lambda', type: 'valid-type4')
136
143
  expect(last_response.status).to eq 200
@@ -156,7 +163,7 @@ describe Grape::Validations::ValuesValidator do
156
163
  it 'raises IncompatibleOptionValues on an invalid default value from proc' do
157
164
  subject = Class.new(Grape::API)
158
165
  expect do
159
- subject.params { optional :type, values: ['valid-type1', 'valid-type2', 'valid-type3'], default: ValuesModel.values.sample + '_invalid' }
166
+ subject.params { optional :type, values: ['valid-type1', 'valid-type2', 'valid-type3'], default: ValidationsSpec::ValuesModel.values.sample + '_invalid' }
160
167
  end.to raise_error Grape::Exceptions::IncompatibleOptionValues
161
168
  end
162
169
 
@@ -174,6 +181,11 @@ describe Grape::Validations::ValuesValidator do
174
181
  end.to raise_error Grape::Exceptions::IncompatibleOptionValues
175
182
  end
176
183
 
184
+ it 'allows values to be true or false when setting the type to boolean' do
185
+ get('/values/optional_boolean', type: true)
186
+ expect(last_response.status).to eq 200
187
+ expect(last_response.body).to eq({ type: true }.to_json)
188
+ end
177
189
  it 'allows values to be a kind of the coerced type not just an instance of it' do
178
190
  get('/values/coercion', type: 10)
179
191
  expect(last_response.status).to eq 200