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.
- checksums.yaml +4 -4
- data/Appraisals +9 -4
- data/CHANGELOG.md +265 -215
- data/CONTRIBUTING.md +4 -4
- data/Gemfile +0 -1
- data/Gemfile.lock +166 -0
- data/README.md +426 -161
- data/RELEASING.md +14 -6
- data/Rakefile +30 -33
- data/UPGRADING.md +54 -23
- data/benchmark/simple.rb +27 -0
- data/gemfiles/rack_1.5.2.gemfile +13 -0
- data/gemfiles/rails_3.gemfile +2 -2
- data/gemfiles/rails_4.gemfile +1 -2
- data/grape.gemspec +6 -7
- data/lib/grape/api.rb +24 -4
- data/lib/grape/dsl/callbacks.rb +20 -0
- data/lib/grape/dsl/configuration.rb +59 -2
- data/lib/grape/dsl/helpers.rb +8 -3
- data/lib/grape/dsl/inside_route.rb +100 -45
- data/lib/grape/dsl/parameters.rb +96 -7
- data/lib/grape/dsl/request_response.rb +1 -1
- data/lib/grape/dsl/routing.rb +17 -4
- data/lib/grape/dsl/settings.rb +36 -1
- data/lib/grape/dsl/validations.rb +7 -5
- data/lib/grape/endpoint.rb +102 -57
- data/lib/grape/error_formatter/base.rb +6 -6
- data/lib/grape/exceptions/base.rb +5 -5
- data/lib/grape/exceptions/invalid_version_header.rb +10 -0
- data/lib/grape/exceptions/unknown_parameter.rb +10 -0
- data/lib/grape/exceptions/validation_errors.rb +4 -3
- data/lib/grape/formatter/serializable_hash.rb +3 -2
- data/lib/grape/http/headers.rb +0 -1
- data/lib/grape/locale/en.yml +5 -1
- data/lib/grape/middleware/auth/base.rb +2 -2
- data/lib/grape/middleware/auth/dsl.rb +1 -1
- data/lib/grape/middleware/auth/strategies.rb +1 -1
- data/lib/grape/middleware/base.rb +8 -4
- data/lib/grape/middleware/error.rb +3 -2
- data/lib/grape/middleware/filter.rb +1 -1
- data/lib/grape/middleware/formatter.rb +64 -45
- data/lib/grape/middleware/globals.rb +3 -3
- data/lib/grape/middleware/versioner/accept_version_header.rb +5 -7
- data/lib/grape/middleware/versioner/header.rb +113 -50
- data/lib/grape/middleware/versioner/param.rb +5 -8
- data/lib/grape/middleware/versioner/parse_media_type_patch.rb +20 -0
- data/lib/grape/middleware/versioner/path.rb +3 -6
- data/lib/grape/namespace.rb +13 -2
- data/lib/grape/path.rb +4 -3
- data/lib/grape/request.rb +40 -0
- data/lib/grape/route.rb +5 -0
- data/lib/grape/util/content_types.rb +9 -9
- data/lib/grape/util/env.rb +22 -0
- data/lib/grape/util/file_response.rb +21 -0
- data/lib/grape/util/inheritable_setting.rb +23 -2
- data/lib/grape/util/inheritable_values.rb +1 -1
- data/lib/grape/util/stackable_values.rb +5 -2
- data/lib/grape/util/strict_hash_configuration.rb +2 -1
- data/lib/grape/validations/attributes_iterator.rb +8 -3
- data/lib/grape/validations/params_scope.rb +164 -22
- data/lib/grape/validations/types/build_coercer.rb +53 -0
- data/lib/grape/validations/types/custom_type_coercer.rb +183 -0
- data/lib/grape/validations/types/file.rb +28 -0
- data/lib/grape/validations/types/json.rb +65 -0
- data/lib/grape/validations/types/multiple_type_coercer.rb +76 -0
- data/lib/grape/validations/types/variant_collection_coercer.rb +59 -0
- data/lib/grape/validations/types/virtus_collection_patch.rb +16 -0
- data/lib/grape/validations/types.rb +144 -0
- data/lib/grape/validations/validators/all_or_none.rb +1 -1
- data/lib/grape/validations/validators/allow_blank.rb +3 -3
- data/lib/grape/validations/validators/base.rb +7 -0
- data/lib/grape/validations/validators/coerce.rb +32 -34
- data/lib/grape/validations/validators/presence.rb +2 -3
- data/lib/grape/validations/validators/regexp.rb +2 -4
- data/lib/grape/validations/validators/values.rb +3 -3
- data/lib/grape/validations.rb +5 -0
- data/lib/grape/version.rb +2 -1
- data/lib/grape.rb +15 -12
- data/pkg/grape-0.13.0.gem +0 -0
- data/spec/grape/api/custom_validations_spec.rb +5 -4
- data/spec/grape/api/deeply_included_options_spec.rb +7 -7
- data/spec/grape/api/nested_helpers_spec.rb +4 -2
- data/spec/grape/api/shared_helpers_spec.rb +8 -8
- data/spec/grape/api_spec.rb +151 -54
- data/spec/grape/dsl/configuration_spec.rb +13 -0
- data/spec/grape/dsl/helpers_spec.rb +16 -2
- data/spec/grape/dsl/inside_route_spec.rb +40 -4
- data/spec/grape/dsl/parameters_spec.rb +0 -6
- data/spec/grape/dsl/routing_spec.rb +1 -1
- data/spec/grape/dsl/validations_spec.rb +18 -0
- data/spec/grape/endpoint_spec.rb +130 -6
- data/spec/grape/entity_spec.rb +10 -8
- data/spec/grape/exceptions/invalid_accept_header_spec.rb +1 -15
- data/spec/grape/exceptions/validation_errors_spec.rb +28 -0
- data/spec/grape/integration/rack_spec.rb +3 -2
- data/spec/grape/middleware/base_spec.rb +40 -16
- data/spec/grape/middleware/error_spec.rb +16 -15
- data/spec/grape/middleware/exception_spec.rb +45 -43
- data/spec/grape/middleware/formatter_spec.rb +34 -5
- data/spec/grape/middleware/versioner/header_spec.rb +79 -47
- data/spec/grape/path_spec.rb +10 -10
- data/spec/grape/presenters/presenter_spec.rb +2 -2
- data/spec/grape/request_spec.rb +100 -0
- data/spec/grape/util/inheritable_values_spec.rb +14 -0
- data/spec/grape/util/stackable_values_spec.rb +10 -0
- data/spec/grape/validations/params_scope_spec.rb +86 -0
- data/spec/grape/validations/types_spec.rb +95 -0
- data/spec/grape/validations/validators/coerce_spec.rb +364 -10
- data/spec/grape/validations/validators/values_spec.rb +27 -15
- data/spec/grape/validations_spec.rb +53 -24
- data/spec/shared/versioning_examples.rb +2 -2
- data/spec/spec_helper.rb +0 -1
- data/spec/support/versioned_helpers.rb +2 -2
- metadata +55 -14
- data/.gitignore +0 -46
- data/.rspec +0 -2
- data/.rubocop.yml +0 -7
- data/.rubocop_todo.yml +0 -84
- data/.travis.yml +0 -20
- data/.yardopts +0 -2
- data/lib/backports/active_support/deep_dup.rb +0 -49
- data/lib/backports/active_support/duplicable.rb +0 -88
- 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 '
|
227
|
+
it 'Rack::Multipart::UploadedFile' do
|
207
228
|
subject.params do
|
208
|
-
requires :file,
|
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
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
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
|
-
|
13
|
-
|
14
|
-
|
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
|