Skip to content

Commit 5683154

Browse files
authored
Merge pull request #1498 from jlfaber/bug/1433/given_block_validation
Skip running param validators inside inactive given blocks (#1433)
2 parents 9385482 + 503b809 commit 5683154

File tree

7 files changed

+148
-89
lines changed

7 files changed

+148
-89
lines changed

CHANGELOG.md

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

1010
#### Fixes
1111

12+
* [#1498](https://github.com/ruby-grape/grape/pull/1498): Skip validations in inactive given blocks - [@jlfaber](https://github.com/jlfaber).
1213
* [#1479](https://github.com/ruby-grape/grape/pull/1479): Support inserting middleware before/after anonymous classes in the middleware stack - [@rosa](https://github.com/rosa).
1314
* [#1488](https://github.com/ruby-grape/grape/pull/1488): Ensure calling before filters when receiving OPTIONS request - [@namusyaka](https://github.com/namusyaka), [@jlfaber](https://github.com/jlfaber).
1415
* [#1493](https://github.com/ruby-grape/grape/pull/1493): Coercion and lambda fails params validation - [@jonmchan](https://github.com/jonmchan).

lib/grape/validations/validators/allow_blank.rb

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,6 @@ def validate_param!(attr_name, params)
77
value = params[attr_name]
88
value = value.strip if value.respond_to?(:strip)
99

10-
key_exists = params.key?(attr_name)
11-
12-
should_validate = if @scope.root?
13-
# root scope. validate if it's a required param. if it's optional, validate only if key exists in hash
14-
@required || key_exists
15-
else # nested scope
16-
(@required && params.present?) ||
17-
# optional param but key inside scoping element exists
18-
(!@required && params.key?(attr_name))
19-
end
20-
21-
return unless should_validate
22-
2310
return if false == value || value.present?
2411

2512
raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: message(:blank)

lib/grape/validations/validators/base.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ def initialize(attrs, options, required, scope)
2424
# @raise [Grape::Exceptions::Validation] if validation failed
2525
# @return [void]
2626
def validate(request)
27+
return unless @scope.should_validate?(request.params)
2728
validate!(request.params)
2829
end
2930

lib/grape/validations/validators/default.rb

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@ def validate_param!(attr_name, params)
1818
end
1919

2020
def validate!(params)
21-
return unless @scope.should_validate?(params)
22-
2321
attrs = AttributesIterator.new(self, @scope, params)
2422
attrs.each do |resource_params, attr_name|
2523
if resource_params.is_a?(Hash) && resource_params[attr_name].nil?

lib/grape/validations/validators/presence.rb

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
11
module Grape
22
module Validations
33
class PresenceValidator < Base
4-
def validate!(params)
5-
return unless @scope.should_validate?(params)
6-
super
7-
end
8-
94
def validate_param!(attr_name, params)
105
return if params.respond_to?(:key?) && params.key?(attr_name)
116
raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], message: message(:presence)

spec/grape/validations/params_scope_spec.rb

Lines changed: 124 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,6 @@ def initialize(value)
181181

182182
subject.post('/required') { 'coercion with proc works' }
183183
post '/required', numbers: '10'
184-
p last_response.body
185184
expect(last_response.status).to eq(201)
186185
expect(last_response.body).to eq('coercion with proc works')
187186
end
@@ -372,6 +371,44 @@ def initialize(value)
372371
expect(last_response.status).to eq(200)
373372
end
374373

374+
it 'applies only the appropriate validation' do
375+
subject.params do
376+
optional :a
377+
optional :b
378+
mutually_exclusive :a, :b
379+
given :a do
380+
requires :c, type: String
381+
end
382+
given :b do
383+
requires :c, type: Integer
384+
end
385+
end
386+
subject.get('/multiple') { declared(params).to_json }
387+
388+
get '/multiple'
389+
expect(last_response.status).to eq(200)
390+
391+
get '/multiple', a: true, c: 'test'
392+
expect(last_response.status).to eq(200)
393+
expect(JSON.parse(last_response.body).symbolize_keys).to eq a: 'true', b: nil, c: 'test'
394+
395+
get '/multiple', b: true, c: '3'
396+
expect(last_response.status).to eq(200)
397+
expect(JSON.parse(last_response.body).symbolize_keys).to eq a: nil, b: 'true', c: 3
398+
399+
get '/multiple', a: true
400+
expect(last_response.status).to eq(400)
401+
expect(last_response.body).to eq('c is missing')
402+
403+
get '/multiple', b: true
404+
expect(last_response.status).to eq(400)
405+
expect(last_response.body).to eq('c is missing')
406+
407+
get '/multiple', a: true, b: true, c: 'test'
408+
expect(last_response.status).to eq(400)
409+
expect(last_response.body).to eq('a, b are mutually exclusive, c is invalid')
410+
end
411+
375412
it 'raises an error if the dependent parameter was never specified' do
376413
expect do
377414
subject.params do
@@ -437,89 +474,114 @@ def initialize(value)
437474
end
438475

439476
context 'when validations are dependent on a parameter with specific value' do
440-
before do
441-
subject.params do
442-
optional :a
443-
given a: ->(val) { val == 'x' } do
444-
requires :b
477+
# build test cases from all combinations of declarations and options
478+
a_decls = %i(optional requires)
479+
a_options = [{}, { values: %w(x y z) }]
480+
b_options = [{}, { type: String }, { allow_blank: false }, { type: String, allow_blank: false }]
481+
combinations = a_decls.product(a_options, b_options)
482+
combinations.each_with_index do |combination, i|
483+
a_decl, a_opts, b_opts = combination
484+
485+
context "(case #{i})" do
486+
before do
487+
# puts "a_decl: #{a_decl}, a_opts: #{a_opts}, b_opts: #{b_opts}"
488+
subject.params do
489+
send a_decl, :a, **a_opts
490+
given(a: ->(val) { val == 'x' }) { requires :b, **b_opts }
491+
given(a: ->(val) { val == 'y' }) { requires :c, **b_opts }
492+
end
493+
subject.get('/test') { declared(params).to_json }
445494
end
446-
end
447-
subject.get('/test') { declared(params).to_json }
448-
end
449-
450-
it 'applies the validations only if the parameter has the specific value' do
451-
get '/test'
452-
expect(last_response.status).to eq(200)
453495

454-
get '/test', a: 'x'
455-
expect(last_response.status).to eq(400)
456-
expect(last_response.body).to eq('b is missing')
496+
if a_decl == :optional
497+
it 'skips validation when base param is missing' do
498+
get '/test'
499+
expect(last_response.status).to eq(200)
500+
end
501+
end
457502

458-
get '/test', a: 'x', b: true
459-
expect(last_response.status).to eq(200)
460-
end
503+
it 'skips validation when base param does not have a specified value' do
504+
get '/test', a: 'z'
505+
expect(last_response.status).to eq(200)
461506

462-
it 'raises an error if the dependent parameter was never specified' do
463-
expect do
464-
subject.params do
465-
given :c do
466-
end
507+
get '/test', a: 'z', b: ''
508+
expect(last_response.status).to eq(200)
467509
end
468-
end.to raise_error(Grape::Exceptions::UnknownParameter)
469-
end
470510

471-
it 'includes the parameter within #declared(params)' do
472-
get '/test', a: true, b: true
511+
it 'applies the validation when base param has the specific value' do
512+
get '/test', a: 'x'
513+
expect(last_response.status).to eq(400)
514+
expect(last_response.body).to include('b is missing')
473515

474-
expect(JSON.parse(last_response.body)).to eq('a' => 'true', 'b' => 'true')
516+
get '/test', a: 'x', b: true
517+
expect(last_response.status).to eq(200)
518+
519+
get '/test', a: 'x', b: true, c: ''
520+
expect(last_response.status).to eq(200)
521+
end
522+
523+
it 'includes the parameter within #declared(params)' do
524+
get '/test', a: 'x', b: true
525+
expect(JSON.parse(last_response.body)).to eq('a' => 'x', 'b' => 'true', 'c' => nil)
526+
end
527+
end
475528
end
529+
end
476530

477-
it 'returns a sensible error message within a nested context' do
531+
it 'raises an error if the dependent parameter was never specified' do
532+
expect do
478533
subject.params do
479-
requires :bar, type: Hash do
480-
optional :a
481-
given a: ->(val) { val == 'x' } do
482-
requires :b
483-
end
534+
given :c do
484535
end
485536
end
486-
subject.get('/nested') { 'worked' }
537+
end.to raise_error(Grape::Exceptions::UnknownParameter)
538+
end
487539

488-
get '/nested', bar: { a: 'x' }
489-
expect(last_response.status).to eq(400)
490-
expect(last_response.body).to eq('bar[b] is missing')
540+
it 'returns a sensible error message within a nested context' do
541+
subject.params do
542+
requires :bar, type: Hash do
543+
optional :a
544+
given a: ->(val) { val == 'x' } do
545+
requires :b
546+
end
547+
end
491548
end
549+
subject.get('/nested') { 'worked' }
492550

493-
it 'includes the nested parameter within #declared(params)' do
494-
subject.params do
495-
requires :bar, type: Hash do
496-
optional :a
497-
given a: ->(val) { val == 'x' } do
498-
requires :b
499-
end
551+
get '/nested', bar: { a: 'x' }
552+
expect(last_response.status).to eq(400)
553+
expect(last_response.body).to eq('bar[b] is missing')
554+
end
555+
556+
it 'includes the nested parameter within #declared(params)' do
557+
subject.params do
558+
requires :bar, type: Hash do
559+
optional :a
560+
given a: ->(val) { val == 'x' } do
561+
requires :b
500562
end
501563
end
502-
subject.get('/nested') { declared(params).to_json }
503-
504-
get '/nested', bar: { a: 'x', b: 'yes' }
505-
expect(JSON.parse(last_response.body)).to eq('bar' => { 'a' => 'x', 'b' => 'yes' })
506564
end
565+
subject.get('/nested') { declared(params).to_json }
507566

508-
it 'includes level 2 nested parameters outside the given within #declared(params)' do
509-
subject.params do
510-
requires :bar, type: Hash do
511-
optional :a
512-
given a: ->(val) { val == 'x' } do
513-
requires :c, type: Hash do
514-
requires :b
515-
end
567+
get '/nested', bar: { a: 'x', b: 'yes' }
568+
expect(JSON.parse(last_response.body)).to eq('bar' => { 'a' => 'x', 'b' => 'yes' })
569+
end
570+
571+
it 'includes level 2 nested parameters outside the given within #declared(params)' do
572+
subject.params do
573+
requires :bar, type: Hash do
574+
optional :a
575+
given a: ->(val) { val == 'x' } do
576+
requires :c, type: Hash do
577+
requires :b
516578
end
517579
end
518580
end
519-
subject.get('/nested') { declared(params).to_json }
520-
521-
get '/nested', bar: { a: 'x', c: { b: 'yes' } }
522-
expect(JSON.parse(last_response.body)).to eq('bar' => { 'a' => 'x', 'c' => { 'b' => 'yes' } })
523581
end
582+
subject.get('/nested') { declared(params).to_json }
583+
584+
get '/nested', bar: { a: 'x', c: { b: 'yes' } }
585+
expect(JSON.parse(last_response.body)).to eq('bar' => { 'a' => 'x', 'c' => { 'b' => 'yes' } })
524586
end
525587
end

spec/grape/validations/validators/allow_blank_spec.rb

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,12 @@ class API < Grape::API
99
params do
1010
requires :name, allow_blank: false
1111
end
12-
get
12+
get '/disallow_blank'
13+
14+
params do
15+
optional :name, type: String, allow_blank: false
16+
end
17+
get '/opt_disallow_string_blank'
1318

1419
params do
1520
optional :name, allow_blank: false
@@ -247,26 +252,31 @@ def app
247252

248253
context 'invalid input' do
249254
it 'refuses empty string' do
250-
get '/', name: ''
255+
get '/disallow_blank', name: ''
251256
expect(last_response.status).to eq(400)
252257

253258
get '/disallow_datetime_blank', val: ''
254259
expect(last_response.status).to eq(400)
255260
end
256261

257262
it 'refuses only whitespaces' do
258-
get '/', name: ' '
263+
get '/disallow_blank', name: ' '
259264
expect(last_response.status).to eq(400)
260265

261-
get '/', name: " \n "
266+
get '/disallow_blank', name: " \n "
262267
expect(last_response.status).to eq(400)
263268

264-
get '/', name: "\n"
269+
get '/disallow_blank', name: "\n"
265270
expect(last_response.status).to eq(400)
266271
end
267272

268273
it 'refuses nil' do
269-
get '/', name: nil
274+
get '/disallow_blank', name: nil
275+
expect(last_response.status).to eq(400)
276+
end
277+
278+
it 'refuses missing' do
279+
get '/disallow_blank'
270280
expect(last_response.status).to eq(400)
271281
end
272282
end
@@ -432,8 +442,13 @@ def app
432442
end
433443

434444
context 'valid input' do
445+
it 'allows missing optional strings' do
446+
get 'opt_disallow_string_blank'
447+
expect(last_response.status).to eq(200)
448+
end
449+
435450
it 'accepts valid input' do
436-
get '/', name: 'bob'
451+
get '/disallow_blank', name: 'bob'
437452
expect(last_response.status).to eq(200)
438453
end
439454

0 commit comments

Comments
 (0)