33module Grape
44 module Validations
55 class ParamsScope
6+ include Grape ::DSL ::Parameters
7+
68 attr_accessor :element , :parent , :index
79 attr_reader :type , :params_meeting_dependency
810
9- include Grape ::DSL ::Parameters
10-
1111 # There are a number of documentation options on entities that don't have
1212 # corresponding validators. Since there is nowhere that enumerates them all,
1313 # we maintain a list of them here and skip looking up validators for them.
1414 RESERVED_DOCUMENTATION_KEYWORDS = %i[ as required param_type is_array format example ] . freeze
1515
16+ ValidatorOptions = Struct . new ( :attributes , :options , :required , :params_scope , :opts )
17+
1618 class Attr
1719 attr_accessor :key , :scope
1820
@@ -85,7 +87,7 @@ def configuration
8587 def should_validate? ( parameters )
8688 scoped_params = params ( parameters )
8789
88- return false if @optional && ( scoped_params . blank? || all_element_blank? ( scoped_params ) )
90+ return false if @optional && scoped_params . blank?
8991 return false unless meets_dependency? ( scoped_params , parameters )
9092 return true if parent . nil?
9193
@@ -325,36 +327,19 @@ def configure_declared_params
325327 def validates ( attrs , validations )
326328 doc = AttributesDoc . new @api , self
327329 doc . extract_details validations
330+ doc . type = infer_coercion_type ( validations )
331+ doc . values = extract_value_option ( validations [ :values ] )
332+ except_values = extract_value_option ( validations [ :except_values ] )
328333
329- coerce_type = infer_coercion ( validations )
330-
331- doc . type = coerce_type
332-
333- default = validations [ :default ]
334- values = validations [ :values ] . is_a? ( Hash ) ? validations . dig ( :values , :value ) : validations [ :values ]
335-
336- doc . values = values
337-
338- except_values = validations [ :except_values ] . is_a? ( Hash ) ? validations . dig ( :except_values , :value ) : validations [ :except_values ]
339-
340- # NB. values and excepts should be nil, Proc, Array, or Range.
341- # Specifically, values should NOT be a Hash
342-
343- # use values or excepts to guess coerce type when stated type is Array
344- coerce_type = guess_coerce_type ( coerce_type , values , except_values )
345-
346- # default value should be present in values array, if both exist and are not procs
347- check_incompatible_option_values ( default , values , except_values )
348-
349- # type should be compatible with values array, if both exist
350- validate_value_coercion ( coerce_type , values , except_values )
334+ check_values_coercing! ( doc . type , doc . values , except_values )
335+ check_default_inclusion! ( validations [ :default ] , doc . values , except_values )
351336
352337 doc . document attrs
353338
354339 opts = derive_validator_options ( validations )
355340
356341 # Validate for presence before any other validators
357- validates_presence ( validations , attrs , doc , opts )
342+ validates_presence ( validations . delete ( :presence ) , attrs , doc , opts )
358343
359344 # Before we run the rest of the validators, let's handle
360345 # whatever coercion so that we are working with correctly
@@ -382,29 +367,16 @@ def validates(attrs, validations)
382367 # parameter declaration
383368 # @return [class-like] type to which the parameter will be coerced
384369 # @raise [ArgumentError] if the given type options are invalid
385- def infer_coercion ( validations )
386- raise ArgumentError , ':type may not be supplied with :types' if validations . key? ( :type ) && validations . key? ( :types )
370+ def infer_coercion_type ( validations )
371+ coerce_options = validations . extract! ( :type , :types )
372+ return if coerce_options . empty?
387373
388- validations [ :coerce ] = ( options_key? ( :type , :value , validations ) ? validations [ :type ] [ :value ] : validations [ :type ] ) if validations . key? ( :type )
389- validations [ :coerce_message ] = ( options_key? ( :type , :message , validations ) ? validations [ :type ] [ :message ] : nil ) if validations . key? ( :type )
390- validations [ :coerce ] = ( options_key? ( :types , :value , validations ) ? validations [ :types ] [ :value ] : validations [ :types ] ) if validations . key? ( :types )
391- validations [ :coerce_message ] = ( options_key? ( :types , :message , validations ) ? validations [ :types ] [ :message ] : nil ) if validations . key? ( :types )
374+ raise ArgumentError , ':type may not be supplied with :types' if coerce_options . size == 2
392375
393- validations . delete ( :types ) if validations . key? ( :types )
394-
395- coerce_type = validations [ :coerce ]
396-
397- # Special case - when the argument is a single type that is a
398- # variant-type collection.
399- if Types . multiple? ( coerce_type ) && validations . key? ( :type )
400- validations [ :coerce ] = Types ::VariantCollectionCoercer . new (
401- coerce_type ,
402- validations . delete ( :coerce_with )
403- )
376+ add_validations_coercion_options ( coerce_options [ :type ] || coerce_options [ :types ] , validations ) . tap do |coerce_type |
377+ # special case of variant-member-type see https://github.com/ruby-grape/grape/tree/master?tab=readme-ov-file#multiple-allowed-types
378+ validations [ :coerce_variant_collection ] = Types . multiple? ( coerce_type ) if coerce_options . key? ( :type )
404379 end
405- validations . delete ( :type )
406-
407- coerce_type
408380 end
409381
410382 # Enforce correct usage of :coerce_with parameter.
@@ -418,7 +390,7 @@ def check_coerce_with(validations)
418390
419391 # but not special JSON types, which
420392 # already imply coercion method
421- return if [ JSON , Array [ JSON ] ] . exclude? validations [ :coerce ]
393+ return if Types :: DISALLOWED_COERCE_TYPES . exclude? validations [ :coerce ]
422394
423395 raise ArgumentError , 'coerce_with disallowed for type: JSON'
424396 end
@@ -432,95 +404,83 @@ def check_coerce_with(validations)
432404 def coerce_type ( validations , attrs , doc , opts )
433405 check_coerce_with ( validations )
434406
435- return unless validations . key? ( :coerce )
407+ coerce_validations_options = validations . extract! ( :coerce , :coerce_with , :coerce_message , :coerce_variant_collection )
408+ return unless coerce_validations_options [ :coerce ]
436409
437410 coerce_options = {
438- type : validations [ :coerce ] ,
439- method : validations [ :coerce_with ] ,
440- message : validations [ :coerce_message ]
411+ type : coerce_validations_options [ :coerce ] ,
412+ method : coerce_validations_options [ :coerce_with ] ,
413+ message : coerce_validations_options [ :coerce_message ] ,
414+ variant_collection : coerce_validations_options [ :coerce_variant_collection ]
441415 }
442- validate ( 'coerce' , coerce_options , attrs , doc , opts )
443- validations . delete ( :coerce_with )
444- validations . delete ( :coerce )
445- validations . delete ( :coerce_message )
446- end
447416
448- def guess_coerce_type ( coerce_type , *values_list )
449- return coerce_type unless coerce_type == Array
450-
451- values_list . each do |values |
452- next if !values || values . is_a? ( Proc )
453- return values . first . class if values . is_a? ( Range ) || !values . empty?
454- end
455- coerce_type
417+ validate ( :coerce , coerce_options , attrs , doc , opts )
456418 end
457419
458- def check_incompatible_option_values ( default , values , except_values )
420+ def check_default_inclusion! ( default , values , except_values )
459421 return unless default && !default . is_a? ( Proc )
460422
461- raise Grape ::Exceptions ::IncompatibleOptionValues . new ( :default , default , :values , values ) if values && !values . is_a? ( Proc ) && !Array ( default ) . all? { |def_val | values . include? ( def_val ) }
423+ raise Grape ::Exceptions ::IncompatibleOptionValues . new ( :default , default , :values , values ) if values && !values . is_a? ( Proc ) && !Array ( default ) . all? { |def_value | values . include? ( def_value ) }
462424
463- return unless except_values && !except_values . is_a? ( Proc ) && Array ( default ) . any? { |def_val | except_values . include? ( def_val ) }
425+ return unless except_values && !except_values . is_a? ( Proc ) && Array ( default ) . any? { |def_value | except_values . include? ( def_value ) }
464426
465427 raise Grape ::Exceptions ::IncompatibleOptionValues . new ( :default , default , :except , except_values )
466428 end
467429
468430 def validate ( type , options , attrs , doc , opts )
469- validator_options = {
470- attributes : attrs ,
471- options : options ,
472- required : doc . required ,
473- params_scope : self ,
474- opts : opts ,
475- validator_class : Validations . require_validator ( type )
476- }
477- @api . namespace_stackable ( :validations , validator_options )
431+ validator_class = Validations . require_validator ( type )
432+ @api . namespace_stackable ( :validations , validator_class . new ( attrs , options , doc . required , self , opts ) )
478433 end
479434
480- def validate_value_coercion ( coerce_type , *values_list )
481- return unless coerce_type
435+ # Validators don't have access to each other and they don't need, however,
436+ # some validators might influence others, so their options should be shared
437+ def derive_validator_options ( validations )
438+ {
439+ allow_blank : extract_value_option ( validations [ :allow_blank ] ) || false ,
440+ fail_fast : validations . delete ( :fail_fast ) || false
441+ }
442+ end
482443
483- coerce_type = coerce_type . first if coerce_type . is_a? ( Enumerable )
484- values_list . each do |values |
485- next if !values || values . is_a? ( Proc )
444+ def validates_presence ( presence , attrs , doc , opts )
445+ return unless presence
486446
487- value_types = values . is_a? ( Range ) ? [ values . begin , values . end ] . compact : values
488- value_types = value_types . map { |type | Grape ::API ::Boolean . build ( type ) } if coerce_type == Grape ::API ::Boolean
489- raise Grape ::Exceptions ::IncompatibleOptionValues . new ( :type , coerce_type , :values , values ) unless value_types . all? ( coerce_type )
490- end
447+ validate ( :presence , presence , attrs , doc , opts )
491448 end
492449
493- def extract_message_option ( attrs )
494- return nil unless attrs . is_a? ( Array )
450+ def extract_value_option ( option )
451+ return option unless option . is_a? ( Hash )
495452
496- opts = attrs . last . is_a? ( Hash ) ? attrs . pop : { }
497- opts . key? ( :message ) && !opts [ :message ] . nil? ? opts . delete ( :message ) : nil
453+ option [ :value ]
498454 end
499455
500- def options_key? ( type , key , validations )
501- validations [ type ] . respond_to? ( :key? ) && validations [ type ] . key? ( key ) && !validations [ type ] [ key ] . nil?
502- end
456+ def extract_message_option ( option )
457+ return unless option . is_a? ( Hash )
503458
504- def all_element_blank? ( scoped_params )
505- scoped_params . respond_to? ( :all? ) && scoped_params . all? ( &:blank? )
459+ option [ :message ]
506460 end
507461
508- # Validators don't have access to each other and they don't need, however,
509- # some validators might influence others, so their options should be shared
510- def derive_validator_options ( validations )
511- allow_blank = validations [ :allow_blank ]
512-
513- {
514- allow_blank : allow_blank . is_a? ( Hash ) ? allow_blank [ :value ] : allow_blank ,
515- fail_fast : validations . delete ( :fail_fast ) || false
516- }
462+ def add_validations_coercion_options ( coercer_options , validations )
463+ if coercer_options . is_a? ( Hash )
464+ options = coercer_options . extract! ( :value , :message )
465+ validations [ :coerce_message ] = options [ :message ]
466+ validations [ :coerce ] = options [ :value ]
467+ else
468+ validations [ :coerce ] = coercer_options
469+ end
517470 end
518471
519- def validates_presence ( validations , attrs , doc , opts )
520- return unless validations . key? ( :presence ) && validations [ :presence ]
472+ def check_values_coercing! ( type , *values_list )
473+ return unless type && values_list . any? { |v | v . present? && !v . is_a? ( Proc ) }
474+
475+ coerce_type = type == Array ? values_list . find ( &:itself ) . first . class : type
476+ coerce_type = coerce_type . first if coerce_type . is_a? ( Enumerable )
477+ values_list . each do |values |
478+ next if values . blank?
521479
522- validate ( 'presence' , validations . delete ( :presence ) , attrs , doc , opts )
523- validations . delete ( :message ) if validations . key? ( :message )
480+ value_types = values . is_a? ( Range ) ? [ values . begin , values . end ] . compact : values
481+ value_types = value_types . map { |type | Grape ::API ::Boolean . build ( type ) } if coerce_type == Grape ::API ::Boolean
482+ raise Grape ::Exceptions ::IncompatibleOptionValues . new ( :type , coerce_type , :values , values ) unless value_types . all? ( coerce_type )
483+ end
524484 end
525485 end
526486 end
0 commit comments