Skip to content

Commit

Permalink
Add nested array coercion spec (#2098)
Browse files Browse the repository at this point in the history
  • Loading branch information
Gwénaël Rault committed Aug 27, 2020
1 parent 192a2a2 commit 7e95ac8
Show file tree
Hide file tree
Showing 5 changed files with 46 additions and 5 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -13,6 +13,7 @@
* [#2091](https://github.com/ruby-grape/grape/pull/2091): Fix ruby 2.7 keyword deprecations - [@dim](https://github.com/dim).
* [#2097](https://github.com/ruby-grape/grape/pull/2097): Skip to set default value unless `meets_dependency?` - [@wanabe](https://github.com/wanabe).
* [#2096](https://github.com/ruby-grape/grape/pull/2096): Fix redundant dependency check - [@braktar](https://github.com/braktar).
* [#2096](https://github.com/ruby-grape/grape/pull/2098): Fix nested coercion - [@braktar](https://github.com/braktar).

### 1.4.0 (2020/07/10)

Expand Down
10 changes: 7 additions & 3 deletions benchmark/large_model.rb
Expand Up @@ -79,7 +79,7 @@ def self.vrp_request_vehicle(this)
this.optional(:cost_time_multiplier, type: Float)

this.optional :router_dimension, type: String, values: %w[time distance]
this.optional(:skills, type: Array[Array[String]])
this.optional(:skills, type: Array[Array[String]], coerce_with: ->(val) { val.is_a?(String) ? [val.split(/,/).map(&:strip)] : val })

this.optional(:unavailable_work_day_indices, type: Array[Integer])

Expand Down Expand Up @@ -224,7 +224,10 @@ def self.vrp_request_schedule(this)
end
end
post '/' do
'hello'
{
skills_v1: params[:vrp][:vehicles].first[:skills],
skills_v2: params[:vrp][:vehicles].last[:skills]
}
end
end
puts Grape::VERSION
Expand All @@ -238,7 +241,8 @@ def self.vrp_request_schedule(this)

start = Time.now
result = RubyProf.profile do
API.call env
response = API.call env
puts response.last
end
puts Time.now - start
printer = RubyProf::FlatPrinter.new(result)
Expand Down
2 changes: 1 addition & 1 deletion benchmark/resource/vrp_example.json

Large diffs are not rendered by default.

14 changes: 13 additions & 1 deletion lib/grape/validations/types/custom_type_coercer.rb
Expand Up @@ -103,13 +103,25 @@ def infer_type_check(type)
# passed, or if the type also implements a parse() method.
type
elsif type.is_a?(Enumerable)
->(value) { value.respond_to?(:all?) && value.all? { |item| item.is_a? type[0] } }
lambda do |value|
value.is_a?(Enumerable) && value.all? do |val|
recursive_type_check(type.first, val)
end
end
else
# By default, do a simple type check
->(value) { value.is_a? type }
end
end

def recursive_type_check(type, value)
if type.is_a?(Enumerable) && value.is_a?(Enumerable)
value.all? { |val| recursive_type_check(type.first, val) }
else
!type.is_a?(Enumerable) && value.is_a?(type)
end
end

# Enforce symbolized keys for complex types
# by wrapping the coercion method such that
# any Hash objects in the immediate heirarchy
Expand Down
24 changes: 24 additions & 0 deletions spec/grape/validations/validators/coerce_spec.rb
Expand Up @@ -620,6 +620,30 @@ def self.parsed?(value)
expect(JSON.parse(last_response.body)).to eq(%w[a b c d])
end

it 'parses parameters with Array[Array[String]] type and coerce_with' do
subject.params do
requires :values, type: Array[Array[String]], coerce_with: ->(val) { val.is_a?(String) ? [val.split(/,/).map(&:strip)] : val }
end
subject.post '/coerce_nested_strings' do
params[:values]
end

post '/coerce_nested_strings', ::Grape::Json.dump(values: 'a,b,c,d'), 'CONTENT_TYPE' => 'application/json'
expect(last_response.status).to eq(201)
expect(JSON.parse(last_response.body)).to eq([%w[a b c d]])

post '/coerce_nested_strings', ::Grape::Json.dump(values: [%w[a c], %w[b]]), 'CONTENT_TYPE' => 'application/json'
expect(last_response.status).to eq(201)
expect(JSON.parse(last_response.body)).to eq([%w[a c], %w[b]])

post '/coerce_nested_strings', ::Grape::Json.dump(values: [[]]), 'CONTENT_TYPE' => 'application/json'
expect(last_response.status).to eq(201)
expect(JSON.parse(last_response.body)).to eq([[]])

post '/coerce_nested_strings', ::Grape::Json.dump(values: [['a', { bar: 0 }], ['b']]), 'CONTENT_TYPE' => 'application/json'
expect(last_response.status).to eq(400)
end

it 'parses parameters with Array[Integer] type' do
subject.params do
requires :values, type: Array[Integer], coerce_with: ->(val) { val.split(/\s+/).map(&:to_i) }
Expand Down

0 comments on commit 7e95ac8

Please sign in to comment.