grape 1.3.1 → 1.3.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +34 -0
  3. data/LICENSE +1 -1
  4. data/README.md +19 -6
  5. data/UPGRADING.md +120 -16
  6. data/lib/grape/api/instance.rb +12 -7
  7. data/lib/grape/dsl/inside_route.rb +37 -14
  8. data/lib/grape/http/headers.rb +1 -0
  9. data/lib/grape/middleware/versioner/header.rb +1 -1
  10. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +2 -1
  11. data/lib/grape/path.rb +2 -2
  12. data/lib/grape/router/attribute_translator.rb +23 -2
  13. data/lib/grape/router/route.rb +3 -22
  14. data/lib/grape/router.rb +6 -14
  15. data/lib/grape/util/base_inheritable.rb +9 -6
  16. data/lib/grape/util/reverse_stackable_values.rb +3 -1
  17. data/lib/grape/util/stackable_values.rb +3 -1
  18. data/lib/grape/validations/types/array_coercer.rb +14 -5
  19. data/lib/grape/validations/types/build_coercer.rb +5 -8
  20. data/lib/grape/validations/types/custom_type_coercer.rb +1 -1
  21. data/lib/grape/validations/types/dry_type_coercer.rb +36 -1
  22. data/lib/grape/validations/types/file.rb +15 -13
  23. data/lib/grape/validations/types/json.rb +40 -36
  24. data/lib/grape/validations/types/primitive_coercer.rb +11 -4
  25. data/lib/grape/validations/types/set_coercer.rb +6 -4
  26. data/lib/grape/validations/types/variant_collection_coercer.rb +1 -1
  27. data/lib/grape/validations/types.rb +6 -5
  28. data/lib/grape/validations/validators/coerce.rb +3 -10
  29. data/lib/grape/validations/validators/default.rb +0 -1
  30. data/lib/grape/validations/validators/regexp.rb +1 -1
  31. data/lib/grape/version.rb +1 -1
  32. data/spec/grape/api/instance_spec.rb +50 -0
  33. data/spec/grape/endpoint_spec.rb +18 -5
  34. data/spec/grape/path_spec.rb +4 -4
  35. data/spec/grape/validations/types/array_coercer_spec.rb +35 -0
  36. data/spec/grape/validations/types/primitive_coercer_spec.rb +5 -1
  37. data/spec/grape/validations/types/set_coercer_spec.rb +34 -0
  38. data/spec/grape/validations/types_spec.rb +1 -1
  39. data/spec/grape/validations/validators/coerce_spec.rb +207 -29
  40. data/spec/grape/validations/validators/default_spec.rb +121 -0
  41. data/spec/grape/validations/validators/values_spec.rb +1 -1
  42. data/spec/grape/validations_spec.rb +5 -5
  43. metadata +9 -5
@@ -7,7 +7,7 @@ describe Grape::Validations::Types::PrimitiveCoercer do
7
7
 
8
8
  subject { described_class.new(type, strict) }
9
9
 
10
- describe '.call' do
10
+ describe '#call' do
11
11
  context 'Boolean' do
12
12
  let(:type) { Grape::API::Boolean }
13
13
 
@@ -26,6 +26,10 @@ describe Grape::Validations::Types::PrimitiveCoercer do
26
26
  it 'returns an error when the given value cannot be coerced' do
27
27
  expect(subject.call(123)).to be_instance_of(Grape::Validations::Types::InvalidValue)
28
28
  end
29
+
30
+ it 'coerces an empty string to nil' do
31
+ expect(subject.call('')).to be_nil
32
+ end
29
33
  end
30
34
 
31
35
  context 'String' do
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Grape::Validations::Types::SetCoercer do
6
+ subject { described_class.new(type) }
7
+
8
+ describe '#call' do
9
+ context 'a set of primitives' do
10
+ let(:type) { Set[String] }
11
+
12
+ it 'coerces elements to the set' do
13
+ expect(subject.call([10, 20])).to eq(Set['10', '20'])
14
+ end
15
+ end
16
+
17
+ context 'a set of sets' do
18
+ let(:type) { Set[Set[Integer]] }
19
+
20
+ it 'coerces elements in the nested set' do
21
+ expect(subject.call([%w[10 20]])).to eq(Set[Set[10, 20]])
22
+ expect(subject.call([['10'], ['20']])).to eq(Set[Set[10], Set[20]])
23
+ end
24
+ end
25
+
26
+ context 'a set of sets of arrays' do
27
+ let(:type) { Set[Set[Array[Integer]]] }
28
+
29
+ it 'coerces elements in the nested set' do
30
+ expect(subject.call([[['10'], ['20']]])).to eq(Set[Set[Array[10], Array[20]]])
31
+ end
32
+ end
33
+ end
34
+ end
@@ -17,7 +17,7 @@ describe Grape::Validations::Types do
17
17
  [
18
18
  Integer, Float, Numeric, BigDecimal,
19
19
  Grape::API::Boolean, String, Symbol,
20
- Date, DateTime, Time, Rack::Multipart::UploadedFile
20
+ Date, DateTime, Time
21
21
  ].each do |type|
22
22
  it "recognizes #{type} as a primitive" do
23
23
  expect(described_class.primitive?(type)).to be_truthy
@@ -154,6 +154,36 @@ describe Grape::Validations::CoerceValidator do
154
154
  end
155
155
 
156
156
  context 'coerces' do
157
+ context 'json' do
158
+ let(:headers) { { 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json' } }
159
+
160
+ it 'BigDecimal' do
161
+ subject.params do
162
+ requires :bigdecimal, type: BigDecimal
163
+ end
164
+ subject.post '/bigdecimal' do
165
+ "#{params[:bigdecimal].class} #{params[:bigdecimal].to_f}"
166
+ end
167
+
168
+ post '/bigdecimal', { bigdecimal: 45.1 }.to_json, headers
169
+ expect(last_response.status).to eq(201)
170
+ expect(last_response.body).to eq('BigDecimal 45.1')
171
+ end
172
+
173
+ it 'Boolean' do
174
+ subject.params do
175
+ requires :boolean, type: Boolean
176
+ end
177
+ subject.post '/boolean' do
178
+ params[:boolean]
179
+ end
180
+
181
+ post '/boolean', { boolean: 'true' }.to_json, headers
182
+ expect(last_response.status).to eq(201)
183
+ expect(last_response.body).to eq('true')
184
+ end
185
+ end
186
+
157
187
  it 'BigDecimal' do
158
188
  subject.params do
159
189
  requires :bigdecimal, coerce: BigDecimal
@@ -180,6 +210,23 @@ describe Grape::Validations::CoerceValidator do
180
210
  expect(last_response.body).to eq(integer_class_name)
181
211
  end
182
212
 
213
+ it 'String' do
214
+ subject.params do
215
+ requires :string, coerce: String
216
+ end
217
+ subject.get '/string' do
218
+ params[:string].class
219
+ end
220
+
221
+ get '/string', string: 45
222
+ expect(last_response.status).to eq(200)
223
+ expect(last_response.body).to eq('String')
224
+
225
+ get '/string', string: nil
226
+ expect(last_response.status).to eq(200)
227
+ expect(last_response.body).to eq('NilClass')
228
+ end
229
+
183
230
  it 'is a custom type' do
184
231
  subject.params do
185
232
  requires :uri, coerce: SecureURIOnly
@@ -307,42 +354,60 @@ describe Grape::Validations::CoerceValidator do
307
354
  expect(last_response.body).to eq('TrueClass')
308
355
  end
309
356
 
310
- it 'Rack::Multipart::UploadedFile' do
311
- subject.params do
312
- requires :file, type: Rack::Multipart::UploadedFile
313
- end
314
- subject.post '/upload' do
315
- params[:file][:filename]
316
- end
357
+ context 'File' do
358
+ let(:file) { Rack::Test::UploadedFile.new(__FILE__) }
359
+ let(:filename) { File.basename(__FILE__).to_s }
317
360
 
318
- post '/upload', file: Rack::Test::UploadedFile.new(__FILE__)
319
- expect(last_response.status).to eq(201)
320
- expect(last_response.body).to eq(File.basename(__FILE__).to_s)
361
+ it 'Rack::Multipart::UploadedFile' do
362
+ subject.params do
363
+ requires :file, type: Rack::Multipart::UploadedFile
364
+ end
365
+ subject.post '/upload' do
366
+ params[:file][:filename]
367
+ end
321
368
 
322
- post '/upload', file: 'not a file'
323
- expect(last_response.status).to eq(400)
324
- expect(last_response.body).to eq('file is invalid')
325
- end
369
+ post '/upload', file: file
370
+ expect(last_response.status).to eq(201)
371
+ expect(last_response.body).to eq(filename)
326
372
 
327
- it 'File' do
328
- subject.params do
329
- requires :file, coerce: File
330
- end
331
- subject.post '/upload' do
332
- params[:file][:filename]
373
+ post '/upload', file: 'not a file'
374
+ expect(last_response.status).to eq(400)
375
+ expect(last_response.body).to eq('file is invalid')
333
376
  end
334
377
 
335
- post '/upload', file: Rack::Test::UploadedFile.new(__FILE__)
336
- expect(last_response.status).to eq(201)
337
- expect(last_response.body).to eq(File.basename(__FILE__).to_s)
378
+ it 'File' do
379
+ subject.params do
380
+ requires :file, coerce: File
381
+ end
382
+ subject.post '/upload' do
383
+ params[:file][:filename]
384
+ end
338
385
 
339
- post '/upload', file: 'not a file'
340
- expect(last_response.status).to eq(400)
341
- expect(last_response.body).to eq('file is invalid')
386
+ post '/upload', file: file
387
+ expect(last_response.status).to eq(201)
388
+ expect(last_response.body).to eq(filename)
342
389
 
343
- post '/upload', file: { filename: 'fake file', tempfile: '/etc/passwd' }
344
- expect(last_response.status).to eq(400)
345
- expect(last_response.body).to eq('file is invalid')
390
+ post '/upload', file: 'not a file'
391
+ expect(last_response.status).to eq(400)
392
+ expect(last_response.body).to eq('file is invalid')
393
+
394
+ post '/upload', file: { filename: 'fake file', tempfile: '/etc/passwd' }
395
+ expect(last_response.status).to eq(400)
396
+ expect(last_response.body).to eq('file is invalid')
397
+ end
398
+
399
+ it 'collection' do
400
+ subject.params do
401
+ requires :files, type: Array[File]
402
+ end
403
+ subject.post '/upload' do
404
+ params[:files].first[:filename]
405
+ end
406
+
407
+ post '/upload', files: [file]
408
+ expect(last_response.status).to eq(201)
409
+ expect(last_response.body).to eq(filename)
410
+ end
346
411
  end
347
412
 
348
413
  it 'Nests integers' do
@@ -359,6 +424,79 @@ describe Grape::Validations::CoerceValidator do
359
424
  expect(last_response.status).to eq(200)
360
425
  expect(last_response.body).to eq(integer_class_name)
361
426
  end
427
+
428
+ context 'nil values' do
429
+ context 'primitive types' do
430
+ Grape::Validations::Types::PRIMITIVES.each do |type|
431
+ it 'respects the nil value' do
432
+ subject.params do
433
+ requires :param, type: type
434
+ end
435
+ subject.get '/nil_value' do
436
+ params[:param].class
437
+ end
438
+
439
+ get '/nil_value', param: nil
440
+ expect(last_response.status).to eq(200)
441
+ expect(last_response.body).to eq('NilClass')
442
+ end
443
+ end
444
+ end
445
+
446
+ context 'structures types' do
447
+ Grape::Validations::Types::STRUCTURES.each do |type|
448
+ it 'respects the nil value' do
449
+ subject.params do
450
+ requires :param, type: type
451
+ end
452
+ subject.get '/nil_value' do
453
+ params[:param].class
454
+ end
455
+
456
+ get '/nil_value', param: nil
457
+ expect(last_response.status).to eq(200)
458
+ expect(last_response.body).to eq('NilClass')
459
+ end
460
+ end
461
+ end
462
+
463
+ context 'special types' do
464
+ Grape::Validations::Types::SPECIAL.each_key do |type|
465
+ it 'respects the nil value' do
466
+ subject.params do
467
+ requires :param, type: type
468
+ end
469
+ subject.get '/nil_value' do
470
+ params[:param].class
471
+ end
472
+
473
+ get '/nil_value', param: nil
474
+ expect(last_response.status).to eq(200)
475
+ expect(last_response.body).to eq('NilClass')
476
+ end
477
+ end
478
+
479
+ context 'variant-member-type collections' do
480
+ [
481
+ Array[Integer, String],
482
+ [Integer, String, Array[Integer, String]]
483
+ ].each do |type|
484
+ it 'respects the nil value' do
485
+ subject.params do
486
+ requires :param, type: type
487
+ end
488
+ subject.get '/nil_value' do
489
+ params[:param].class
490
+ end
491
+
492
+ get '/nil_value', param: nil
493
+ expect(last_response.status).to eq(200)
494
+ expect(last_response.body).to eq('NilClass')
495
+ end
496
+ end
497
+ end
498
+ end
499
+ end
362
500
  end
363
501
 
364
502
  context 'using coerce_with' do
@@ -478,6 +616,46 @@ describe Grape::Validations::CoerceValidator do
478
616
  expect(last_response.body).to eq('3')
479
617
  end
480
618
 
619
+ context 'Integer type and coerce_with potentially returning nil' do
620
+ before do
621
+ subject.params do
622
+ requires :int, type: Integer, coerce_with: (lambda do |val|
623
+ if val == '0'
624
+ nil
625
+ elsif val.match?(/^-?\d+$/)
626
+ val.to_i
627
+ else
628
+ val
629
+ end
630
+ end)
631
+ end
632
+ subject.get '/' do
633
+ params[:int].class.to_s
634
+ end
635
+ end
636
+
637
+ it 'accepts value that coerces to nil' do
638
+ get '/', int: '0'
639
+
640
+ expect(last_response.status).to eq(200)
641
+ expect(last_response.body).to eq('NilClass')
642
+ end
643
+
644
+ it 'coerces to Integer' do
645
+ get '/', int: '1'
646
+
647
+ expect(last_response.status).to eq(200)
648
+ expect(last_response.body).to eq('Integer')
649
+ end
650
+
651
+ it 'returns invalid value if coercion returns a wrong type' do
652
+ get '/', int: 'lol'
653
+
654
+ expect(last_response.status).to eq(400)
655
+ expect(last_response.body).to eq('int is invalid')
656
+ end
657
+ end
658
+
481
659
  it 'must be supplied with :type or :coerce' do
482
660
  expect do
483
661
  subject.params do
@@ -298,4 +298,125 @@ describe Grape::Validations::DefaultValidator do
298
298
  end
299
299
  end
300
300
  end
301
+
302
+ context 'optional with nil as value' do
303
+ subject do
304
+ Class.new(Grape::API) do
305
+ default_format :json
306
+ end
307
+ end
308
+
309
+ def app
310
+ subject
311
+ end
312
+
313
+ context 'primitive types' do
314
+ [
315
+ [Integer, 0],
316
+ [Integer, 42],
317
+ [Float, 0.0],
318
+ [Float, 4.2],
319
+ [BigDecimal, 0.0],
320
+ [BigDecimal, 4.2],
321
+ [Numeric, 0],
322
+ [Numeric, 42],
323
+ [Date, Date.today],
324
+ [DateTime, DateTime.now],
325
+ [Time, Time.now],
326
+ [Time, Time.at(0)],
327
+ [Grape::API::Boolean, false],
328
+ [String, ''],
329
+ [String, 'non-empty-string'],
330
+ [Symbol, :symbol],
331
+ [TrueClass, true],
332
+ [FalseClass, false]
333
+ ].each do |type, default|
334
+ it 'respects the default value' do
335
+ subject.params do
336
+ optional :param, type: type, default: default
337
+ end
338
+ subject.get '/default_value' do
339
+ params[:param]
340
+ end
341
+
342
+ get '/default_value', param: nil
343
+ expect(last_response.status).to eq(200)
344
+ expect(last_response.body).to eq(default.to_json)
345
+ end
346
+ end
347
+ end
348
+
349
+ context 'structures types' do
350
+ [
351
+ [Hash, {}],
352
+ [Hash, { test: 'non-empty' }],
353
+ [Array, []],
354
+ [Array, ['non-empty']],
355
+ [Array[Integer], []],
356
+ [Set, []],
357
+ [Set, [1]]
358
+ ].each do |type, default|
359
+ it 'respects the default value' do
360
+ subject.params do
361
+ optional :param, type: type, default: default
362
+ end
363
+ subject.get '/default_value' do
364
+ params[:param]
365
+ end
366
+
367
+ get '/default_value', param: nil
368
+ expect(last_response.status).to eq(200)
369
+ expect(last_response.body).to eq(default.to_json)
370
+ end
371
+ end
372
+ end
373
+
374
+ context 'special types' do
375
+ [
376
+ [JSON, ''],
377
+ [JSON, { test: 'non-empty-string' }.to_json],
378
+ [Array[JSON], []],
379
+ [Array[JSON], [{ test: 'non-empty-string' }.to_json]],
380
+ [::File, ''],
381
+ [::File, { test: 'non-empty-string' }.to_json],
382
+ [Rack::Multipart::UploadedFile, ''],
383
+ [Rack::Multipart::UploadedFile, { test: 'non-empty-string' }.to_json]
384
+ ].each do |type, default|
385
+ it 'respects the default value' do
386
+ subject.params do
387
+ optional :param, type: type, default: default
388
+ end
389
+ subject.get '/default_value' do
390
+ params[:param]
391
+ end
392
+
393
+ get '/default_value', param: nil
394
+ expect(last_response.status).to eq(200)
395
+ expect(last_response.body).to eq(default.to_json)
396
+ end
397
+ end
398
+ end
399
+
400
+ context 'variant-member-type collections' do
401
+ [
402
+ [Array[Integer, String], [0, '']],
403
+ [Array[Integer, String], [42, 'non-empty-string']],
404
+ [[Integer, String, Array[Integer, String]], [0, '', [0, '']]],
405
+ [[Integer, String, Array[Integer, String]], [42, 'non-empty-string', [42, 'non-empty-string']]]
406
+ ].each do |type, default|
407
+ it 'respects the default value' do
408
+ subject.params do
409
+ optional :param, type: type, default: default
410
+ end
411
+ subject.get '/default_value' do
412
+ params[:param]
413
+ end
414
+
415
+ get '/default_value', param: nil
416
+ expect(last_response.status).to eq(200)
417
+ expect(last_response.body).to eq(default.to_json)
418
+ end
419
+ end
420
+ end
421
+ end
301
422
  end
@@ -319,7 +319,7 @@ describe Grape::Validations::ValuesValidator do
319
319
  expect(last_response.status).to eq 200
320
320
  end
321
321
 
322
- it 'allows for an optional param with a list of values' do
322
+ it 'accepts for an optional param with a list of values' do
323
323
  put('/optional_with_array_of_string_values', optional: nil)
324
324
  expect(last_response.status).to eq 200
325
325
  end
@@ -574,7 +574,7 @@ describe Grape::Validations do
574
574
  # NOTE: with body parameters in json or XML or similar this
575
575
  # should actually fail with: children[parents][name] is missing.
576
576
  expect(last_response.status).to eq(400)
577
- expect(last_response.body).to eq('children[1][parents] is missing')
577
+ expect(last_response.body).to eq('children[1][parents] is missing, children[0][parents][1][name] is missing, children[0][parents][1][name] is empty')
578
578
  end
579
579
 
580
580
  it 'errors when a parameter is not present in array within array' do
@@ -615,7 +615,7 @@ describe Grape::Validations do
615
615
 
616
616
  get '/within_array', children: [name: 'Jay']
617
617
  expect(last_response.status).to eq(400)
618
- expect(last_response.body).to eq('children[0][parents] is missing')
618
+ expect(last_response.body).to eq('children[0][parents] is missing, children[0][parents][0][name] is missing, children[0][parents][0][name] is empty')
619
619
  end
620
620
 
621
621
  it 'errors when param is not an Array' do
@@ -763,7 +763,7 @@ describe Grape::Validations do
763
763
  expect(last_response.status).to eq(200)
764
764
  put_with_json '/within_array', children: [name: 'Jay']
765
765
  expect(last_response.status).to eq(400)
766
- expect(last_response.body).to eq('children[0][parents] is missing')
766
+ expect(last_response.body).to eq('children[0][parents] is missing, children[0][parents][0][name] is missing')
767
767
  end
768
768
  end
769
769
 
@@ -838,7 +838,7 @@ describe Grape::Validations do
838
838
  it 'does internal validations if the outer group is present' do
839
839
  get '/nested_optional_group', items: [{ key: 'foo' }]
840
840
  expect(last_response.status).to eq(400)
841
- expect(last_response.body).to eq('items[0][required_subitems] is missing')
841
+ expect(last_response.body).to eq('items[0][required_subitems] is missing, items[0][required_subitems][0][value] is missing')
842
842
 
843
843
  get '/nested_optional_group', items: [{ key: 'foo', required_subitems: [{ value: 'bar' }] }]
844
844
  expect(last_response.status).to eq(200)
@@ -858,7 +858,7 @@ describe Grape::Validations do
858
858
  it 'handles validation within arrays' do
859
859
  get '/nested_optional_group', items: [{ key: 'foo' }]
860
860
  expect(last_response.status).to eq(400)
861
- expect(last_response.body).to eq('items[0][required_subitems] is missing')
861
+ expect(last_response.body).to eq('items[0][required_subitems] is missing, items[0][required_subitems][0][value] is missing')
862
862
 
863
863
  get '/nested_optional_group', items: [{ key: 'foo', required_subitems: [{ value: 'bar' }] }]
864
864
  expect(last_response.status).to eq(200)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: grape
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.1
4
+ version: 1.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Bleigh
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-03-11 00:00:00.000000000 Z
11
+ date: 2020-05-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -332,7 +332,9 @@ files:
332
332
  - spec/grape/validations/multiple_attributes_iterator_spec.rb
333
333
  - spec/grape/validations/params_scope_spec.rb
334
334
  - spec/grape/validations/single_attribute_iterator_spec.rb
335
+ - spec/grape/validations/types/array_coercer_spec.rb
335
336
  - spec/grape/validations/types/primitive_coercer_spec.rb
337
+ - spec/grape/validations/types/set_coercer_spec.rb
336
338
  - spec/grape/validations/types_spec.rb
337
339
  - spec/grape/validations/validators/all_or_none_spec.rb
338
340
  - spec/grape/validations/validators/allow_blank_spec.rb
@@ -364,9 +366,9 @@ licenses:
364
366
  - MIT
365
367
  metadata:
366
368
  bug_tracker_uri: https://github.com/ruby-grape/grape/issues
367
- changelog_uri: https://github.com/ruby-grape/grape/blob/v1.3.1/CHANGELOG.md
368
- documentation_uri: https://www.rubydoc.info/gems/grape/1.3.1
369
- source_code_uri: https://github.com/ruby-grape/grape/tree/v1.3.1
369
+ changelog_uri: https://github.com/ruby-grape/grape/blob/v1.3.3/CHANGELOG.md
370
+ documentation_uri: https://www.rubydoc.info/gems/grape/1.3.3
371
+ source_code_uri: https://github.com/ruby-grape/grape/tree/v1.3.3
370
372
  post_install_message:
371
373
  rdoc_options: []
372
374
  require_paths:
@@ -415,6 +417,8 @@ test_files:
415
417
  - spec/grape/api_remount_spec.rb
416
418
  - spec/grape/validations/types_spec.rb
417
419
  - spec/grape/validations/attributes_iterator_spec.rb
420
+ - spec/grape/validations/types/array_coercer_spec.rb
421
+ - spec/grape/validations/types/set_coercer_spec.rb
418
422
  - spec/grape/validations/types/primitive_coercer_spec.rb
419
423
  - spec/grape/validations/validators/regexp_spec.rb
420
424
  - spec/grape/validations/validators/default_spec.rb