33module Grape
44 module Validations
55 class ParamsScope
6+ include Grape ::DSL ::Parameters
7+
68 attr_accessor :element , :parent , :index
79 attr_reader :type
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
@@ -84,7 +86,7 @@ def configuration
8486 def should_validate? ( parameters )
8587 scoped_params = params ( parameters )
8688
87- return false if @optional && ( scoped_params . blank? || all_element_blank? ( scoped_params ) )
89+ return false if @optional && scoped_params . blank?
8890 return false unless meets_dependency? ( scoped_params , parameters )
8991 return true if parent . nil?
9092
@@ -320,43 +322,19 @@ def configure_declared_params
320322 def validates ( attrs , validations )
321323 doc = AttributesDoc . new @api , self
322324 doc . extract_details validations
325+ doc . type = infer_coercion_type ( validations )
326+ doc . values = extract_value_option ( validations [ :values ] )
327+ except_values = extract_value_option ( validations [ :except_values ] )
323328
324- coerce_type = infer_coercion ( validations )
325-
326- doc . type = coerce_type
327-
328- default = validations [ :default ]
329-
330- if ( values_hash = validations [ :values ] ) . is_a? Hash
331- values = values_hash [ :value ]
332- # NB: excepts is deprecated
333- excepts = values_hash [ :except ]
334- else
335- values = validations [ :values ]
336- end
337-
338- doc . values = values
339-
340- except_values = options_key? ( :except_values , :value , validations ) ? validations [ :except_values ] [ :value ] : validations [ :except_values ]
341-
342- # NB. values and excepts should be nil, Proc, Array, or Range.
343- # Specifically, values should NOT be a Hash
344-
345- # use values or excepts to guess coerce type when stated type is Array
346- coerce_type = guess_coerce_type ( coerce_type , values , except_values , excepts )
347-
348- # default value should be present in values array, if both exist and are not procs
349- check_incompatible_option_values ( default , values , except_values , excepts )
350-
351- # type should be compatible with values array, if both exist
352- validate_value_coercion ( coerce_type , values , except_values , excepts )
329+ check_values_coercing! ( doc . type , doc . values , except_values )
330+ check_default_inclusion! ( validations [ :default ] , doc . values , except_values )
353331
354332 doc . document attrs
355333
356334 opts = derive_validator_options ( validations )
357335
358336 # Validate for presence before any other validators
359- validates_presence ( validations , attrs , doc , opts )
337+ validates_presence ( validations . delete ( :presence ) , attrs , doc , opts )
360338
361339 # Before we run the rest of the validators, let's handle
362340 # whatever coercion so that we are working with correctly
@@ -384,29 +362,16 @@ def validates(attrs, validations)
384362 # parameter declaration
385363 # @return [class-like] type to which the parameter will be coerced
386364 # @raise [ArgumentError] if the given type options are invalid
387- def infer_coercion ( validations )
388- raise ArgumentError , ':type may not be supplied with :types' if validations . key? ( :type ) && validations . key? ( :types )
389-
390- validations [ :coerce ] = ( options_key? ( :type , :value , validations ) ? validations [ :type ] [ :value ] : validations [ :type ] ) if validations . key? ( :type )
391- validations [ :coerce_message ] = ( options_key? ( :type , :message , validations ) ? validations [ :type ] [ :message ] : nil ) if validations . key? ( :type )
392- validations [ :coerce ] = ( options_key? ( :types , :value , validations ) ? validations [ :types ] [ :value ] : validations [ :types ] ) if validations . key? ( :types )
393- validations [ :coerce_message ] = ( options_key? ( :types , :message , validations ) ? validations [ :types ] [ :message ] : nil ) if validations . key? ( :types )
394-
395- validations . delete ( :types ) if validations . key? ( :types )
365+ def infer_coercion_type ( validations )
366+ coerce_options = validations . extract! ( :type , :types )
367+ return if coerce_options . empty?
396368
397- coerce_type = validations [ :coerce ]
369+ raise ArgumentError , ':type may not be supplied with :types' if coerce_options . size == 2
398370
399- # Special case - when the argument is a single type that is a
400- # variant-type collection.
401- if Types . multiple? ( coerce_type ) && validations . key? ( :type )
402- validations [ :coerce ] = Types ::VariantCollectionCoercer . new (
403- coerce_type ,
404- validations . delete ( :coerce_with )
405- )
371+ add_validations_coercion_options ( coerce_options [ :type ] || coerce_options [ :types ] , validations ) . tap do |coerce_type |
372+ # special case of variant-member-type see https://github.com/ruby-grape/grape/tree/master?tab=readme-ov-file#multiple-allowed-types
373+ validations [ :coerce_variant_collection ] = Types . multiple? ( coerce_type ) if coerce_options . key? ( :type )
406374 end
407- validations . delete ( :type )
408-
409- coerce_type
410375 end
411376
412377 # Enforce correct usage of :coerce_with parameter.
@@ -420,7 +385,7 @@ def check_coerce_with(validations)
420385
421386 # but not special JSON types, which
422387 # already imply coercion method
423- return if [ JSON , Array [ JSON ] ] . exclude? validations [ :coerce ]
388+ return if Types :: DISALLOWED_COERCE_TYPES . exclude? validations [ :coerce ]
424389
425390 raise ArgumentError , 'coerce_with disallowed for type: JSON'
426391 end
@@ -434,99 +399,83 @@ def check_coerce_with(validations)
434399 def coerce_type ( validations , attrs , doc , opts )
435400 check_coerce_with ( validations )
436401
437- return unless validations . key? ( :coerce )
402+ coerce_validations_options = validations . extract! ( :coerce , :coerce_with , :coerce_message , :coerce_variant_collection )
403+ return unless coerce_validations_options [ :coerce ]
438404
439405 coerce_options = {
440- type : validations [ :coerce ] ,
441- method : validations [ :coerce_with ] ,
442- message : validations [ :coerce_message ]
406+ type : coerce_validations_options [ :coerce ] ,
407+ method : coerce_validations_options [ :coerce_with ] ,
408+ message : coerce_validations_options [ :coerce_message ] ,
409+ variant_collection : coerce_validations_options [ :coerce_variant_collection ]
443410 }
444- validate ( 'coerce' , coerce_options , attrs , doc , opts )
445- validations . delete ( :coerce_with )
446- validations . delete ( :coerce )
447- validations . delete ( :coerce_message )
448- end
449-
450- def guess_coerce_type ( coerce_type , *values_list )
451- return coerce_type unless coerce_type == Array
452411
453- values_list . each do |values |
454- next if !values || values . is_a? ( Proc )
455- return values . first . class if values . is_a? ( Range ) || !values . empty?
456- end
457- coerce_type
412+ validate ( :coerce , coerce_options , attrs , doc , opts )
458413 end
459414
460- def check_incompatible_option_values ( default , values , except_values , excepts )
415+ def check_default_inclusion! ( default , values , except_values )
461416 return unless default && !default . is_a? ( Proc )
462417
463- raise Grape ::Exceptions ::IncompatibleOptionValues . new ( :default , default , :values , values ) if values && !values . is_a? ( Proc ) && !Array ( default ) . all? { |def_val | values . include? ( def_val ) }
418+ raise Grape ::Exceptions ::IncompatibleOptionValues . new ( :default , default , :values , values ) if values && !values . is_a? ( Proc ) && !Array ( default ) . all? { |def_value | values . include? ( def_value ) }
464419
465- if except_values && !except_values . is_a? ( Proc ) && Array ( default ) . any? { |def_val | except_values . include? ( def_val ) }
466- raise Grape ::Exceptions ::IncompatibleOptionValues . new ( :default , default , :except , except_values )
467- end
420+ return unless except_values && !except_values . is_a? ( Proc ) && Array ( default ) . any? { |def_value | except_values . include? ( def_value ) }
468421
469- return unless excepts && !excepts . is_a? ( Proc )
470- raise Grape ::Exceptions ::IncompatibleOptionValues . new ( :default , default , :except , excepts ) \
471- unless Array ( default ) . none? { |def_val | excepts . include? ( def_val ) }
422+ raise Grape ::Exceptions ::IncompatibleOptionValues . new ( :default , default , :except , except_values )
472423 end
473424
474425 def validate ( type , options , attrs , doc , opts )
475- validator_options = {
476- attributes : attrs ,
477- options : options ,
478- required : doc . required ,
479- params_scope : self ,
480- opts : opts ,
481- validator_class : Validations . require_validator ( type )
482- }
483- @api . namespace_stackable ( :validations , validator_options )
426+ validator_class = Validations . require_validator ( type )
427+ @api . namespace_stackable ( :validations , validator_class . new ( attrs , options , doc . required , self , opts ) )
484428 end
485429
486- def validate_value_coercion ( coerce_type , *values_list )
487- return unless coerce_type
430+ # Validators don't have access to each other and they don't need, however,
431+ # some validators might influence others, so their options should be shared
432+ def derive_validator_options ( validations )
433+ {
434+ allow_blank : extract_value_option ( validations [ :allow_blank ] ) || false ,
435+ fail_fast : validations . delete ( :fail_fast ) || false
436+ }
437+ end
488438
489- coerce_type = coerce_type . first if coerce_type . is_a? ( Enumerable )
490- values_list . each do |values |
491- next if !values || values . is_a? ( Proc )
439+ def validates_presence ( presence , attrs , doc , opts )
440+ return unless presence
492441
493- value_types = values . is_a? ( Range ) ? [ values . begin , values . end ] . compact : values
494- value_types = value_types . map { |type | Grape ::API ::Boolean . build ( type ) } if coerce_type == Grape ::API ::Boolean
495- raise Grape ::Exceptions ::IncompatibleOptionValues . new ( :type , coerce_type , :values , values ) unless value_types . all? ( coerce_type )
496- end
442+ validate ( :presence , presence , attrs , doc , opts )
497443 end
498444
499- def extract_message_option ( attrs )
500- return nil unless attrs . is_a? ( Array )
445+ def extract_value_option ( option )
446+ return option unless option . is_a? ( Hash )
501447
502- opts = attrs . last . is_a? ( Hash ) ? attrs . pop : { }
503- opts . key? ( :message ) && !opts [ :message ] . nil? ? opts . delete ( :message ) : nil
448+ option [ :value ]
504449 end
505450
506- def options_key? ( type , key , validations )
507- validations [ type ] . respond_to? ( :key? ) && validations [ type ] . key? ( key ) && !validations [ type ] [ key ] . nil?
508- end
451+ def extract_message_option ( option )
452+ return unless option . is_a? ( Hash )
509453
510- def all_element_blank? ( scoped_params )
511- scoped_params . respond_to? ( :all? ) && scoped_params . all? ( &:blank? )
454+ option [ :message ]
512455 end
513456
514- # Validators don't have access to each other and they don't need, however,
515- # some validators might influence others, so their options should be shared
516- def derive_validator_options ( validations )
517- allow_blank = validations [ :allow_blank ]
518-
519- {
520- allow_blank : allow_blank . is_a? ( Hash ) ? allow_blank [ :value ] : allow_blank ,
521- fail_fast : validations . delete ( :fail_fast ) || false
522- }
457+ def add_validations_coercion_options ( coercer_options , validations )
458+ if coercer_options . is_a? ( Hash )
459+ options = coercer_options . extract! ( :value , :message )
460+ validations [ :coerce_message ] = options [ :message ]
461+ validations [ :coerce ] = options [ :value ]
462+ else
463+ validations [ :coerce ] = coercer_options
464+ end
523465 end
524466
525- def validates_presence ( validations , attrs , doc , opts )
526- return unless validations . key? ( :presence ) && validations [ :presence ]
467+ def check_values_coercing! ( type , * values_list )
468+ return unless type && values_list . any? { | v | v . present? && ! v . is_a? ( Proc ) }
527469
528- validate ( 'presence' , validations . delete ( :presence ) , attrs , doc , opts )
529- validations . delete ( :message ) if validations . key? ( :message )
470+ coerce_type = type == Array ? values_list . find ( &:itself ) . first . class : type
471+ coerce_type = coerce_type . first if coerce_type . is_a? ( Enumerable )
472+ values_list . each do |values |
473+ next if values . blank?
474+
475+ value_types = values . is_a? ( Range ) ? [ values . begin , values . end ] . compact : values
476+ value_types = value_types . map { |type | Grape ::API ::Boolean . build ( type ) } if coerce_type == Grape ::API ::Boolean
477+ raise Grape ::Exceptions ::IncompatibleOptionValues . new ( :type , coerce_type , :values , values ) unless value_types . all? ( coerce_type )
478+ end
530479 end
531480 end
532481 end
0 commit comments