Skip to content

Commit

Permalink
Fix the declared with not given (#2285).
Browse files Browse the repository at this point in the history
  • Loading branch information
zysend authored Nov 2, 2022
1 parent 62562f8 commit 0e727c1
Show file tree
Hide file tree
Showing 7 changed files with 309 additions and 44 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
* [#2272](https://github.com/ruby-grape/grape/pull/2272): Added error on param init when provided type does not have `[]` coercion method, previously validation silently failed for any value - [@vasfed](https://github.com/Vasfed).
* [#2274](https://github.com/ruby-grape/grape/pull/2274): Error middleware support using rack util's symbols as status - [@dhruvCW](https://github.com/dhruvCW).
* [#2276](https://github.com/ruby-grape/grape/pull/2276): Fix exception super - [@ericproulx](https://github.com/ericproulx).
* [#2285](https://github.com/ruby-grape/grape/pull/2285): Added :evaluate_given to declared(params) - [@zysend](https://github.com/zysend).
* Your contribution here.

#### Fixes
Expand Down
97 changes: 97 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
- [Declared](#declared)
- [Include Parent Namespaces](#include-parent-namespaces)
- [Include Missing](#include-missing)
- [Evaluate Given](#evaluate-given)
- [Parameter Validation and Coercion](#parameter-validation-and-coercion)
- [Supported Parameter Types](#supported-parameter-types)
- [Integer/Fixnum and Coercions](#integerfixnum-and-coercions)
Expand Down Expand Up @@ -1078,6 +1079,102 @@ curl -X POST -H "Content-Type: application/json" localhost:9292/users/signup -d
}
````

### Evaluate Given

By default `declared(params)` will not evaluate `given` and return all parameters. Use `evaluate_given` to evaluate all `given` blocks and return only parameters that satisfy `given` conditions. Consider the following API:

````ruby
format :json

params do
optional :child_id, type: Integer
given :child_id do
requires :father_id, type: Integer
end
end

post 'child' do
{ 'declared_params' => declared(params, evaluate_given: true) }
end
````

**Request**

````bash
curl -X POST -H "Content-Type: application/json" localhost:9292/child -d '{"father_id": 1}'
````

**Response with evaluate_given:false**

````json
{
"declared_params": {
"child_id": null,
"father_id": 1
}
}
````

**Response with evaluate_given:true**

````json
{
"declared_params": {
"child_id": null
}
}
````

It also works on nested hashes:

````ruby
format :json

params do
requires :child, type: Hash do
optional :child_id, type: Integer
given :child_id do
requires :father_id, type: Integer
end
end
end

post 'child' do
{ 'declared_params' => declared(params, evaluate_given: true) }
end
````

**Request**

````bash
curl -X POST -H "Content-Type: application/json" localhost:9292/child -d '{"child": {"father_id": 1}}'
````

**Response with evaluate_given:false**

````json
{
"declared_params": {
"child": {
"child_id": null,
"father_id": 1
}
}
}
````

**Response with evaluate_given:true**

````json
{
"declared_params": {
"child": {
"child_id": null
}
}
}
````

## Parameter Validation and Coercion

You can define validations and coercion options for your parameters using a `params` block.
Expand Down
61 changes: 33 additions & 28 deletions lib/grape/dsl/inside_route.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def self.post_filter_methods(type)
# has completed
module PostBeforeFilter
def declared(passed_params, options = {}, declared_params = nil, params_nested_path = [])
options = options.reverse_merge(include_missing: true, include_parent_namespaces: true)
options = options.reverse_merge(include_missing: true, include_parent_namespaces: true, evaluate_given: false, request_params: passed_params)
declared_params ||= optioned_declared_params(**options)

if passed_params.is_a?(Array)
Expand All @@ -47,41 +47,46 @@ def declared_array(passed_params, options, declared_params, params_nested_path)
end

def declared_hash(passed_params, options, declared_params, params_nested_path)
renamed_params = route_setting(:renamed_params) || {}
declared_params.each_with_object(passed_params.class.new) do |declared_param_attr, memo|
next if options[:evaluate_given] && !declared_param_attr.meets_dependency?(options[:request_params])

declared_params.each_with_object(passed_params.class.new) do |declared_param, memo|
if declared_param.is_a?(Hash)
declared_param.each_pair do |declared_parent_param, declared_children_params|
params_nested_path_dup = params_nested_path.dup
params_nested_path_dup << declared_parent_param.to_s
next unless options[:include_missing] || passed_params.key?(declared_parent_param)
declared_hash_attr(passed_params, options, declared_param_attr.key, params_nested_path, memo)
end
end

rename_path = params_nested_path + [declared_parent_param.to_s]
renamed_param_name = renamed_params[rename_path]
def declared_hash_attr(passed_params, options, declared_param, params_nested_path, memo)
renamed_params = route_setting(:renamed_params) || {}
if declared_param.is_a?(Hash)
declared_param.each_pair do |declared_parent_param, declared_children_params|
params_nested_path_dup = params_nested_path.dup
params_nested_path_dup << declared_parent_param.to_s
next unless options[:include_missing] || passed_params.key?(declared_parent_param)

rename_path = params_nested_path + [declared_parent_param.to_s]
renamed_param_name = renamed_params[rename_path]

memo_key = optioned_param_key(renamed_param_name || declared_parent_param, options)
passed_children_params = passed_params[declared_parent_param] || passed_params.class.new
memo_key = optioned_param_key(renamed_param_name || declared_parent_param, options)
passed_children_params = passed_params[declared_parent_param] || passed_params.class.new

memo[memo_key] = handle_passed_param(params_nested_path_dup, passed_children_params.any?) do
declared(passed_children_params, options, declared_children_params, params_nested_path_dup)
end
memo[memo_key] = handle_passed_param(params_nested_path_dup, passed_children_params.any?) do
declared(passed_children_params, options, declared_children_params, params_nested_path_dup)
end
else
# If it is not a Hash then it does not have children.
# Find its value or set it to nil.
next unless options[:include_missing] || passed_params.key?(declared_param)
end
else
# If it is not a Hash then it does not have children.
# Find its value or set it to nil.
return unless options[:include_missing] || passed_params.key?(declared_param)

rename_path = params_nested_path + [declared_param.to_s]
renamed_param_name = renamed_params[rename_path]
rename_path = params_nested_path + [declared_param.to_s]
renamed_param_name = renamed_params[rename_path]

memo_key = optioned_param_key(renamed_param_name || declared_param, options)
passed_param = passed_params[declared_param]
memo_key = optioned_param_key(renamed_param_name || declared_param, options)
passed_param = passed_params[declared_param]

params_nested_path_dup = params_nested_path.dup
params_nested_path_dup << declared_param.to_s
memo[memo_key] = passed_param || handle_passed_param(params_nested_path_dup) do
passed_param
end
params_nested_path_dup = params_nested_path.dup
params_nested_path_dup << declared_param.to_s
memo[memo_key] = passed_param || handle_passed_param(params_nested_path_dup) do
passed_param
end
end
end
Expand Down
4 changes: 2 additions & 2 deletions lib/grape/dsl/parameters.rb
Original file line number Diff line number Diff line change
Expand Up @@ -217,8 +217,8 @@ def declared_param?(param)
else
# @declared_params also includes hashes of options and such, but those
# won't be flattened out.
@declared_params.flatten.any? do |declared_param|
first_hash_key_or_param(declared_param) == param
@declared_params.flatten.any? do |declared_param_attr|
first_hash_key_or_param(declared_param_attr.key) == param
end
end
end
Expand Down
38 changes: 37 additions & 1 deletion lib/grape/validations/params_scope.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,41 @@ class ParamsScope

include Grape::DSL::Parameters

class Attr
attr_accessor :key, :scope

# Open up a new ParamsScope::Attr
# @param key [Hash, Symbol] key of attr
# @param scope [Grape::Validations::ParamsScope] scope of attr
def initialize(key, scope)
@key = key
@scope = scope
end

def meets_dependency?(request_params)
return true if scope.nil?

scope.meets_dependency?(scope.params(request_params), request_params)
end

# @return Array[Symbol, Hash[Symbol => Array]] declared_params with symbol instead of Attr
def self.attrs_keys(declared_params)
declared_params.map do |declared_param_attr|
attr_key(declared_param_attr)
end
end

def self.attr_key(declared_param_attr)
return attr_key(declared_param_attr.key) if declared_param_attr.is_a?(self)

if declared_param_attr.is_a?(Hash)
declared_param_attr.transform_values { |value| attrs_keys(value) }
else
declared_param_attr
end
end
end

# Open up a new ParamsScope, allowing parameter definitions per
# Grape::DSL::Params.
# @param opts [Hash] options for this scope
Expand Down Expand Up @@ -130,13 +165,14 @@ def required?
# Adds a parameter declaration to our list of validations.
# @param attrs [Array] (see Grape::DSL::Parameters#requires)
def push_declared_params(attrs, **opts)
opts = opts.merge(declared_params_scope: self) unless opts.key?(:declared_params_scope)
if lateral?
@parent.push_declared_params(attrs, **opts)
else
push_renamed_param(full_path + [attrs.first], opts[:as]) \
if opts && opts[:as]

@declared_params.concat attrs
@declared_params.concat(attrs.map { |attr| ::Grape::Validations::ParamsScope::Attr.new(attr, opts[:declared_params_scope]) })
end
end

Expand Down
Loading

0 comments on commit 0e727c1

Please sign in to comment.