diff --git a/CHANGELOG.md b/CHANGELOG.md index 879f1f918..dabfb17a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ * [#3071](https://github.com/ruby-grape/grape/pull/3071): Skip the query parse when building the params of a request with no query string - [@ericproulx](https://github.com/ericproulx). * [#3072](https://github.com/ruby-grape/grape/pull/3072): Answer the `Accept` lists axios, jQuery and HTTPie send without negotiating them on every request - [@ericproulx](https://github.com/ericproulx). * [#3073](https://github.com/ruby-grape/grape/pull/3073): Let a `rescue_from :all` in a namespace or mounted API outrank the enclosing scope's handlers - [@ericproulx](https://github.com/ericproulx). +* [#3075](https://github.com/ruby-grape/grape/pull/3075): Validate the params of an optional Hash scope without the attributes iterator - [@ericproulx](https://github.com/ericproulx). * [#3076](https://github.com/ruby-grape/grape/pull/3076): Skip the query merge when building the params of a body sent without a query string - [@ericproulx](https://github.com/ericproulx). * Your contribution here. diff --git a/lib/grape/validations/validators/base.rb b/lib/grape/validations/validators/base.rb index 63db6377d..e20a94fa0 100644 --- a/lib/grape/validations/validators/base.rb +++ b/lib/grape/validations/validators/base.rb @@ -121,17 +121,19 @@ def validate_param!(attr_name, params) # time a validator is built. See #validate and #validate!. def choose_validation_path(scope) @always_validated = scope.always_validated? - @direct = @always_validated && !scope.iterates_elements? + @direct = scope.independent? && scope.array_depth.zero? @direct_elements = scope.validated_when_given? && scope.iterates_elements? && scope.array_depth == 1 end - # #validate! on a scope that always validates and does not iterate - # elements, once its params resolved to a Hash: the root scope and the - # required Hash scopes under it, which is most validators of most - # endpoints. There the iterator hands back that Hash alone, and every - # scope on the chain is required with no dependency, so the checks - # around the attribute loop drop out. The machinery cost more than - # the validation itself. + # #validate! on a scope with no Array scope and no +given+ on its + # chain, once its params resolved to a Hash: the root scope and the + # Hash scopes under it, required or optional, which is most validators + # of most endpoints. There the iterator hands back that Hash alone, + # and no scope on the chain has a dependency to meet. An optional one + # is only passed over when its params are empty, and #validate does + # not get this far with those (see ParamsScope#should_validate?). So + # the checks around the attribute loop drop out. The machinery cost + # more than the validation itself. def validate_attributes!(params) array_errors = nil diff --git a/lib/grape/validations/validators/default_validator.rb b/lib/grape/validations/validators/default_validator.rb index d918d946e..6625adc4e 100644 --- a/lib/grape/validations/validators/default_validator.rb +++ b/lib/grape/validations/validators/default_validator.rb @@ -31,10 +31,11 @@ def validate!(params) private # Also #validate!'s whole job where Base#validate_attributes! applies: - # a scope that is always validated and does not iterate elements, once - # its params resolved to a Hash -- the root scope of every +params+ - # block with a default in it. A scope like that depends on no other - # param, so the iterator and its dependency check have nothing to add. + # a scope with no Array scope and no +given+ on its chain, once its + # params resolved to a Hash -- the root scope of every +params+ block + # with a default in it, and the Hash scopes under it. A scope like + # that depends on no other param, so the iterator and its dependency + # check have nothing to add. def apply_defaults(params) @attrs.each do |attr_name| params[attr_name] = @default_call.call(params) if params[attr_name].nil?