Skip to content

Commit be04f92

Browse files
committed
Add is_a?(Array) to coerce_nil because of Array with a type.
Add CHANGELOG.md Add documentation in README.md Add specs for future proof return nil instead of InvalidValue Remove Structures Implicit Default Value section Not returning when param's value is nil Remove default value for structures Add specs for nil values and default values Add non-empty value spec when params is nil Typo change Rubocop
1 parent e0dfb9c commit be04f92

File tree

9 files changed

+211
-36
lines changed

9 files changed

+211
-36
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#### Fixes
1010

1111
* Your contribution here.
12+
* [#2040](https://github.com/ruby-grape/grape/pull/2040): Fix a regression with Array of type nil - [@ericproulx](https://github.com/ericproulx).
1213

1314
### 1.3.2 (2020/04/12)
1415

lib/grape/validations/types/variant_collection_coercer.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def initialize(types, method = nil)
3333
# the coerced result, or an instance
3434
# of {InvalidValue} if the value could not be coerced.
3535
def call(value)
36-
return InvalidValue.new unless value.is_a? Array
36+
return unless value.is_a? Array
3737

3838
value =
3939
if @method

lib/grape/validations/validators/coerce.rb

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -67,21 +67,12 @@ def valid_type?(val)
6767
end
6868

6969
def coerce_value(val)
70-
val.nil? ? coerce_nil(val) : converter.call(val)
70+
converter.call(val)
7171
# Some custom types might fail, so it should be treated as an invalid value
7272
rescue StandardError
7373
Types::InvalidValue.new
7474
end
7575

76-
def coerce_nil(val)
77-
# define default values for structures, the dry-types lib which is used
78-
# for coercion doesn't accept nil as a value, so it would fail
79-
return [] if type == Array
80-
return Set.new if type == Set
81-
return {} if type == Hash
82-
val
83-
end
84-
8576
# Type to which the parameter will be coerced.
8677
#
8778
# @return [Class]

lib/grape/validations/validators/default.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ def initialize(attrs, options, required, scope, opts = {})
99
end
1010

1111
def validate_param!(attr_name, params)
12-
return if params.key? attr_name
1312
params[attr_name] = if @default.is_a? Proc
1413
@default.call
1514
elsif @default.frozen? || !duplicatable?(@default)

spec/grape/validations/validators/coerce_spec.rb

Lines changed: 74 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,80 @@ def self.parsed?(value)
424424
expect(last_response.status).to eq(200)
425425
expect(last_response.body).to eq(integer_class_name)
426426
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+
p last_response.body
494+
expect(last_response.status).to eq(200)
495+
expect(last_response.body).to eq('NilClass')
496+
end
497+
end
498+
end
499+
end
500+
end
427501
end
428502

429503
context 'using coerce_with' do
@@ -752,19 +826,6 @@ def self.parsed?(value)
752826
expect(last_response.body).to eq('String')
753827
end
754828

755-
it 'respects nil values' do
756-
subject.params do
757-
optional :a, types: [File, String]
758-
end
759-
subject.get '/' do
760-
params[:a].class.to_s
761-
end
762-
763-
get '/', a: nil
764-
expect(last_response.status).to eq(200)
765-
expect(last_response.body).to eq('NilClass')
766-
end
767-
768829
it 'fails when no coercion is possible' do
769830
subject.params do
770831
requires :a, types: [Boolean, Integer]

spec/grape/validations/validators/default_spec.rb

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,4 +298,124 @@ def app
298298
end
299299
end
300300
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+
[Set, []],
356+
[Set, [1]]
357+
].each do |type, default|
358+
it 'respects the default value' do
359+
subject.params do
360+
optional :param, type: type, default: default
361+
end
362+
subject.get '/default_value' do
363+
params[:param]
364+
end
365+
366+
get '/default_value', param: nil
367+
expect(last_response.status).to eq(200)
368+
expect(last_response.body).to eq(default.to_json)
369+
end
370+
end
371+
end
372+
373+
context 'special types' do
374+
[
375+
[JSON, ''],
376+
[JSON, { test: 'non-empty-json' }.to_json],
377+
[Array[JSON], []],
378+
[Array[JSON], [{ test: 'non-empty-json' }.to_json]],
379+
[::File, ''],
380+
[::File, { test: 'non-empty-json' }.to_json],
381+
[Rack::Multipart::UploadedFile, ''],
382+
[Rack::Multipart::UploadedFile, { test: 'non-empty-json' }.to_json]
383+
].each do |type, default|
384+
it 'respects the default value' do
385+
subject.params do
386+
requires :param, type: type, default: default
387+
end
388+
subject.get '/default_value' do
389+
params[:param]
390+
end
391+
392+
get '/default_value', param: nil
393+
expect(last_response.status).to eq(200)
394+
expect(last_response.body).to eq(default.to_json)
395+
end
396+
end
397+
end
398+
399+
context 'variant-member-type collections' do
400+
[
401+
[Array[Integer, String], [0, '']],
402+
[Array[Integer, String], [42, 'non-empty-string']],
403+
[[Integer, String, Array[Integer, String]], [0, '', [0, '']]],
404+
[[Integer, String, Array[Integer, String]], [42, 'non-empty-string', [42, 'non-empty-string']]]
405+
].each do |type, default|
406+
it 'respects the default value' do
407+
subject.params do
408+
requires :param, type: type, default: default
409+
end
410+
subject.get '/default_value' do
411+
params[:param]
412+
end
413+
414+
get '/default_value', param: nil
415+
expect(last_response.status).to eq(200)
416+
expect(last_response.body).to eq(default.to_json)
417+
end
418+
end
419+
end
420+
end
301421
end

spec/grape/validations/validators/regexp_spec.rb

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,10 @@ def app
9898
expect(last_response.status).to eq(200)
9999
end
100100

101-
it 'accepts nil instead of array' do
101+
it 'refuses nil instead of array' do
102102
get '/custom_message/regexp_with_array', names: nil
103-
expect(last_response.status).to eq(200)
103+
expect(last_response.status).to eq(400)
104+
expect(last_response.body).to eq('{"error":"names can\'t be nil"}')
104105
end
105106
end
106107
end
@@ -153,9 +154,10 @@ def app
153154
expect(last_response.status).to eq(200)
154155
end
155156

156-
it 'accepts nil instead of array' do
157+
it 'rejects nil instead of array' do
157158
get '/regexp_with_array', names: nil
158-
expect(last_response.status).to eq(200)
159+
expect(last_response.status).to eq(400)
160+
expect(last_response.body).to eq('{"error":"names is invalid"}')
159161
end
160162
end
161163

spec/grape/validations/validators/values_spec.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -319,9 +319,10 @@ def app
319319
expect(last_response.status).to eq 200
320320
end
321321

322-
it 'allows for an optional param with a list of values' do
322+
it 'refuses for an optional param with a list of values' do
323323
put('/optional_with_array_of_string_values', optional: nil)
324-
expect(last_response.status).to eq 200
324+
expect(last_response.status).to eq 400
325+
expect(last_response.body).to eq({ error: 'optional is invalid' }.to_json)
325326
end
326327
end
327328

spec/grape/validations_spec.rb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -574,7 +574,7 @@ def validate_param!(attr_name, params)
574574
# NOTE: with body parameters in json or XML or similar this
575575
# should actually fail with: children[parents][name] is missing.
576576
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')
578578
end
579579

580580
it 'errors when a parameter is not present in array within array' do
@@ -615,7 +615,7 @@ def validate_param!(attr_name, params)
615615

616616
get '/within_array', children: [name: 'Jay']
617617
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')
619619
end
620620

621621
it 'errors when param is not an Array' do
@@ -763,7 +763,7 @@ def validate_param!(attr_name, params)
763763
expect(last_response.status).to eq(200)
764764
put_with_json '/within_array', children: [name: 'Jay']
765765
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')
767767
end
768768
end
769769

@@ -838,7 +838,7 @@ def validate_param!(attr_name, params)
838838
it 'does internal validations if the outer group is present' do
839839
get '/nested_optional_group', items: [{ key: 'foo' }]
840840
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')
842842

843843
get '/nested_optional_group', items: [{ key: 'foo', required_subitems: [{ value: 'bar' }] }]
844844
expect(last_response.status).to eq(200)
@@ -858,7 +858,7 @@ def validate_param!(attr_name, params)
858858
it 'handles validation within arrays' do
859859
get '/nested_optional_group', items: [{ key: 'foo' }]
860860
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')
862862

863863
get '/nested_optional_group', items: [{ key: 'foo', required_subitems: [{ value: 'bar' }] }]
864864
expect(last_response.status).to eq(200)

0 commit comments

Comments
 (0)