diff --git a/CHANGELOG.md b/CHANGELOG.md index 649d56741..42ebd9ffa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,7 @@ * [#2719](https://github.com/ruby-grape/grape/pull/2719): Move content-type helpers from `Middleware::Base` into `PrecomputedContentTypes` - [@ericproulx](https://github.com/ericproulx). * [#2716](https://github.com/ruby-grape/grape/pull/2716): Refactor `DSL::Routing#version`: guard clause, explicit kwargs in place of `**options`, and a `Grape::DSL::VersionOptions` value object stored internally - [@ericproulx](https://github.com/ericproulx). * [#2720](https://github.com/ruby-grape/grape/pull/2720): Move declaration-coherence checks into `Grape::Validations::ValidationsSpec` - [@ericproulx](https://github.com/ericproulx). +* [#2725](https://github.com/ruby-grape/grape/pull/2725): Encapsulate `Grape::Validations::Validators::Base` state behind readers; add `required?`/`allow_blank?` predicates - [@ericproulx](https://github.com/ericproulx). * Your contribution here. #### Fixes diff --git a/lib/grape/validations/validators/all_or_none_of_validator.rb b/lib/grape/validations/validators/all_or_none_of_validator.rb index 69e9bc530..2ab86ef53 100644 --- a/lib/grape/validations/validators/all_or_none_of_validator.rb +++ b/lib/grape/validations/validators/all_or_none_of_validator.rb @@ -9,7 +9,7 @@ class AllOrNoneOfValidator < MultipleParamsBase def validate_params!(params) known_keys = all_keys keys = keys_in_common(params, known_keys) - return if keys.empty? || keys.length == @attrs.length + return if keys.empty? || keys.length == attrs.length validation_error!(known_keys) end diff --git a/lib/grape/validations/validators/at_least_one_of_validator.rb b/lib/grape/validations/validators/at_least_one_of_validator.rb index 55b95ee91..6c6239654 100644 --- a/lib/grape/validations/validators/at_least_one_of_validator.rb +++ b/lib/grape/validations/validators/at_least_one_of_validator.rb @@ -8,7 +8,7 @@ class AtLeastOneOfValidator < MultipleParamsBase def validate_params!(params) known_keys = all_keys - return if hash_like?(params) && known_keys.intersect?(params.keys.map { |attr| @scope.full_name(attr) }) + return if hash_like?(params) && known_keys.intersect?(params.keys.map { |attr| scope.full_name(attr) }) validation_error!(known_keys) end diff --git a/lib/grape/validations/validators/base.rb b/lib/grape/validations/validators/base.rb index 2c290a9c1..eb4440cec 100644 --- a/lib/grape/validations/validators/base.rb +++ b/lib/grape/validations/validators/base.rb @@ -23,6 +23,9 @@ class Base # the public 5th-argument contract stays a plain Hash. def_delegators :@opts, :allow_blank, :fail_fast + alias fail_fast? fail_fast + alias allow_blank? allow_blank + class << self # Declares the default I18n message key used by +validation_error!+. # Subclasses that only need a single fixed error message can declare it @@ -77,15 +80,11 @@ def initialize(attrs, options, required, scope, opts) # @raise [Grape::Exceptions::Validation] if validation failed # @return [void] def validate(request) - return unless @scope.should_validate?(request.params) + return unless scope.should_validate?(request.params) validate!(request.params) end - def fail_fast? - fail_fast - end - # Validates a given parameter hash. # @note Override #validate_param! for per-parameter validation, # or #validate if you need access to the entire request. @@ -93,16 +92,16 @@ def fail_fast? # @raise [Grape::Exceptions::Validation] if validation failed # @return [void] def validate!(params) - attributes = SingleAttributeIterator.new(@attrs, @scope, params) + attributes = SingleAttributeIterator.new(attrs, scope, params) # we collect errors inside array because # there may be more than one error per field array_errors = nil attributes.each do |val, attr_name, empty_val| - next if !@scope.required? && empty_val - next unless @scope.meets_dependency?(val, params) + next if !scope.required? && empty_val + next unless scope.meets_dependency?(val, params) - validate_param!(attr_name, val) if @required || (hash_like?(val) && val.key?(attr_name)) + validate_param!(attr_name, val) if required? || (hash_like?(val) && val.key?(attr_name)) rescue Grape::Exceptions::Validation => e (array_errors ||= []) << e end @@ -123,8 +122,12 @@ def validate_param!(attr_name, params) private - def validation_error!(attr_name_or_params, message = @exception_message) - params = attr_name_or_params.is_a?(Array) ? attr_name_or_params : @scope.full_name(attr_name_or_params) + attr_reader :options, :scope, :required, :exception_message + + alias required? required + + def validation_error!(attr_name_or_params, message = exception_message) + params = attr_name_or_params.is_a?(Array) ? attr_name_or_params : scope.full_name(attr_name_or_params) raise Grape::Exceptions::Validation.new(params:, message:) end @@ -132,8 +135,8 @@ def hash_like?(obj) obj.respond_to?(:key?) end - def options_key?(key, options = nil) - current_options = options || @options + def options_key?(key, given_options = nil) + current_options = given_options || options hash_like?(current_options) && current_options.key?(key) && !current_options[key].nil? end @@ -145,14 +148,14 @@ def options_key?(key, options = nil) # @exception_message = message(:presence) # symbol key or custom message # @exception_message = message { build_hash_message } # computed fallback def message(default_key = nil) - key = options_key?(:message) ? @options[:message] : default_key + key = options_key?(:message) ? options[:message] : default_key return key unless key.nil? yield if block_given? end def option_value - options_key?(:value) ? @options[:value] : @options + options_key?(:value) ? options[:value] : options end def scrub(value) diff --git a/lib/grape/validations/validators/coerce_validator.rb b/lib/grape/validations/validators/coerce_validator.rb index 477bf5d6b..c970f0a5a 100644 --- a/lib/grape/validations/validators/coerce_validator.rb +++ b/lib/grape/validations/validators/coerce_validator.rb @@ -9,18 +9,15 @@ class CoerceValidator < Base def initialize(attrs, options, required, scope, opts) super - # +@options+ is a Grape::Validations::CoerceOptions. +Base#message+ - # can't see a custom message off a Data (it probes Hash-like - # +key?+), so restore it here to preserve `type: { value:, message: }`. - @exception_message = @options.message unless @options.message.nil? + @exception_message = options.message if options.message - raw_type = @options.type + raw_type = options.type type = hash_like?(raw_type) ? raw_type[:value] : raw_type @converter = if type.is_a?(Grape::Validations::Types::VariantCollectionCoercer) type else - Types.build_coercer(type, method: @options.coerce_method) + Types.build_coercer(type, method: options.coerce_method) end end @@ -29,7 +26,7 @@ def validate_param!(attr_name, params) new_value = coerce_value(params[attr_name]) - validation_error!(attr_name, new_value.message || @exception_message) if new_value.is_a?(Types::InvalidValue) + validation_error!(attr_name, new_value.message || exception_message) if new_value.is_a?(Types::InvalidValue) # Don't assign a value if it is identical. It fixes a problem with Hashie::Mash # which looses wrappers for hashes and arrays after reassigning values diff --git a/lib/grape/validations/validators/default_validator.rb b/lib/grape/validations/validators/default_validator.rb index 5b57b499d..7f03c0ed9 100644 --- a/lib/grape/validations/validators/default_validator.rb +++ b/lib/grape/validations/validators/default_validator.rb @@ -8,19 +8,19 @@ def initialize(attrs, options, required, scope, opts) super # !important, lazy call at runtime @default_call = - if @options.is_a?(Proc) - @options.arity.zero? ? proc { @options.call } : @options - elsif @options.duplicable? - proc { @options.dup } + if options.is_a?(Proc) + options.arity.zero? ? proc { options.call } : options + elsif options.duplicable? + proc { options.dup } else - proc { @options } + proc { options } end end def validate!(params) - attrs = SingleAttributeIterator.new(@attrs, @scope, params) - attrs.each do |resource_params, attr_name| - next unless @scope.meets_dependency?(resource_params, params) + attributes = SingleAttributeIterator.new(attrs, scope, params) + attributes.each do |resource_params, attr_name| + next unless scope.meets_dependency?(resource_params, params) resource_params[attr_name] = @default_call.call(resource_params) if hash_like?(resource_params) && resource_params[attr_name].nil? end diff --git a/lib/grape/validations/validators/length_validator.rb b/lib/grape/validations/validators/length_validator.rb index 7d94a9226..06712693a 100644 --- a/lib/grape/validations/validators/length_validator.rb +++ b/lib/grape/validations/validators/length_validator.rb @@ -7,7 +7,7 @@ class LengthValidator < Base def initialize(attrs, options, required, scope, opts) super - @min, @max, @is = @options.values_at(:min, :max, :is) + @min, @max, @is = options.values_at(:min, :max, :is) validate_boundary!(:min, @min) validate_boundary!(:max, @max) raise ArgumentError, "min #{@min} cannot be greater than max #{@max}" if @min && @max && @min > @max diff --git a/lib/grape/validations/validators/multiple_params_base.rb b/lib/grape/validations/validators/multiple_params_base.rb index 1ba846982..b31bbd229 100644 --- a/lib/grape/validations/validators/multiple_params_base.rb +++ b/lib/grape/validations/validators/multiple_params_base.rb @@ -5,7 +5,7 @@ module Validations module Validators class MultipleParamsBase < Base def validate!(params) - attributes = MultipleAttributesIterator.new(@attrs, @scope, params) + attributes = MultipleAttributesIterator.new(attrs, scope, params) array_errors = [] attributes.each do |resource_params| @@ -22,11 +22,11 @@ def validate!(params) def keys_in_common(resource_params, known_keys = all_keys) return [] unless hash_like?(resource_params) - known_keys & resource_params.keys.map! { |attr| @scope.full_name(attr) } + known_keys & resource_params.keys.map! { |attr| scope.full_name(attr) } end def all_keys - @attrs.map { |attr| @scope.full_name(attr) } + attrs.map { |attr| scope.full_name(attr) } end end end diff --git a/lib/grape/validations/validators/oneof_validator.rb b/lib/grape/validations/validators/oneof_validator.rb index 9cc9579ee..5d7cbe55e 100644 --- a/lib/grape/validations/validators/oneof_validator.rb +++ b/lib/grape/validations/validators/oneof_validator.rb @@ -19,7 +19,7 @@ def initialize(attrs, options, required, scope, opts) def validate_param!(attr_name, params) value = params[attr_name] - return if value.nil? && !@required + return if value.nil? && !required? winning_candidate = nil @variants.each do |variant_validators| diff --git a/lib/grape/validations/validators/values_validator.rb b/lib/grape/validations/validators/values_validator.rb index fa53f3bb5..93c2af3bd 100644 --- a/lib/grape/validations/validators/values_validator.rb +++ b/lib/grape/validations/validators/values_validator.rb @@ -28,7 +28,7 @@ def validate_param!(attr_name, params) val = scrub(params[attr_name]) return if val.nil? && !required_for_root_scope? - return if val != false && val.blank? && allow_blank + return if val != false && val.blank? && allow_blank? return if check_values?(val, attr_name) validation_error!(attr_name) @@ -55,12 +55,12 @@ def check_values?(val, attr_name) end def required_for_root_scope? - return false unless @required + return false unless required? - scope = @scope - scope = scope.parent while scope.lateral? + current_scope = scope + current_scope = current_scope.parent while current_scope.lateral? - scope.root? + current_scope.root? end end end