Skip to content

Provides Array index context in errors. #1218

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 7, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

* Your contribution here.

* [#1218](https://github.com/ruby-grape/grape/pull/1218): Provide array index context in errors - [@towanda](https://github.com/towanda).
* [#1196](https://github.com/ruby-grape/grape/pull/1196): Allow multiple `before_each` blocks - [@huynhquancam](https://github.com/huynhquancam).
* [#1190](https://github.com/ruby-grape/grape/putt/1190): Bypass formatting for statuses with no entity-body - [@tylerdooling](https://github.com/tylerdooling).
* [#1188](https://github.com/ruby-grape/grape/putt/1188): Allow parameters with more than one type - [@dslh](https://github.com/dslh).
Expand Down
8 changes: 7 additions & 1 deletion lib/grape/validations/attributes_iterator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,20 @@ module Validations
class AttributesIterator
include Enumerable

attr_reader :scope

def initialize(validator, scope, params)
@scope = scope
@attrs = validator.attrs
@params = Array.wrap(scope.params(params))
end

def each
@params.each do |resource_params|
@attrs.each do |attr_name|
@attrs.each_with_index do |attr_name, index|
if resource_params.is_a?(Hash) && resource_params[attr_name].is_a?(Array)
scope.index = index
end
yield resource_params, attr_name
end
end
Expand Down
8 changes: 6 additions & 2 deletions lib/grape/validations/params_scope.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module Grape
module Validations
class ParamsScope
attr_accessor :element, :parent
attr_accessor :element, :parent, :index

include Grape::DSL::Parameters

Expand Down Expand Up @@ -46,7 +46,7 @@ def full_name(name)
case
when nested?
# Find our containing element's name, and append ours.
"#{@parent.full_name(@element)}[#{name}]"
"#{@parent.full_name(@element)}#{parent_index}[#{name}]"
when lateral?
# Find the name of the element as if it was at the
# same nesting level as our parent.
Expand All @@ -57,6 +57,10 @@ def full_name(name)
end
end

def parent_index
"[#{@parent.index}]" if @parent.present? && @parent.index.present?
end

# @return [Boolean] whether or not this scope is the root-level scope
def root?
!@parent
Expand Down
2 changes: 1 addition & 1 deletion spec/grape/validations/validators/coerce_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ class User

get '/ints', ints: '{"i":1,"j":"2"}'
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('ints[i] is missing, ints[i] is invalid, ints[j] is missing')
expect(last_response.body).to eq('ints[0][i] is missing, ints[0][i] is invalid, ints[0][j] is missing')

get '/ints', ints: '[{"i":"1","j":"2"}]'
expect(last_response.status).to eq(200)
Expand Down
22 changes: 11 additions & 11 deletions spec/grape/validations_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ def define_requires_none

get '/required', items: [{ key: 'hash in array' }]
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('items is invalid, items[key] does not have a valid value')
expect(last_response.body).to eq('items is invalid, items[0][key] does not have a valid value')
end

it 'works when all params match' do
Expand Down Expand Up @@ -492,7 +492,7 @@ def validate_param!(attr_name, params)
# NOTE: with body parameters in json or XML or similar this
# should actually fail with: children[parents][name] is missing.
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('children[parents] is missing')
expect(last_response.body).to eq('children[0][parents] is missing')
end

it 'safely handles empty arrays and blank parameters' do
Expand All @@ -503,7 +503,7 @@ def validate_param!(attr_name, params)
expect(last_response.body).to eq('children is missing')
get '/within_array', children: [name: 'Jay']
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('children[parents] is missing')
expect(last_response.body).to eq('children[0][parents] is missing')
end

it 'errors when param is not an Array' do
Expand All @@ -518,7 +518,7 @@ def validate_param!(attr_name, params)

get '/within_array', children: [name: 'Jay', parents: { name: 'Fred' }]
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('children[parents] is invalid')
expect(last_response.body).to eq('children[0][parents] is invalid')
end
end

Expand Down Expand Up @@ -644,15 +644,15 @@ def validate_param!(attr_name, params)
{ name: 'Job', parents: [{ name: 'Joy' }] }
]
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('children[parents][name] is missing')
expect(last_response.body).to eq('children[0][parents][0][name] is missing')
end

it 'safely handles empty arrays and blank parameters' do
put_with_json '/within_array', children: []
expect(last_response.status).to eq(200)
put_with_json '/within_array', children: [name: 'Jay']
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('children[parents] is missing')
expect(last_response.body).to eq('children[0][parents] is missing')
end
end

Expand Down Expand Up @@ -683,7 +683,7 @@ def validate_param!(attr_name, params)
it 'errors when group is present, but required param is not' do
get '/optional_group', items: [{ not_key: 'foo' }]
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('items[key] is missing')
expect(last_response.body).to eq('items[0][key] is missing')
end

it "errors when param is present but isn't an Array" do
Expand Down Expand Up @@ -727,7 +727,7 @@ def validate_param!(attr_name, params)
it 'does internal validations if the outer group is present' do
get '/nested_optional_group', items: [{ key: 'foo' }]
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('items[required_subitems] is missing')
expect(last_response.body).to eq('items[0][required_subitems] is missing')

get '/nested_optional_group', items: [{ key: 'foo', required_subitems: [{ value: 'bar' }] }]
expect(last_response.status).to eq(200)
Expand All @@ -737,7 +737,7 @@ def validate_param!(attr_name, params)
it 'handles deep nesting' do
get '/nested_optional_group', items: [{ key: 'foo', required_subitems: [{ value: 'bar' }], optional_subitems: [{ not_value: 'baz' }] }]
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('items[optional_subitems][value] is missing')
expect(last_response.body).to eq('items[0][optional_subitems][0][value] is missing')

get '/nested_optional_group', items: [{ key: 'foo', required_subitems: [{ value: 'bar' }], optional_subitems: [{ value: 'baz' }] }]
expect(last_response.status).to eq(200)
Expand All @@ -747,15 +747,15 @@ def validate_param!(attr_name, params)
it 'handles validation within arrays' do
get '/nested_optional_group', items: [{ key: 'foo' }]
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('items[required_subitems] is missing')
expect(last_response.body).to eq('items[0][required_subitems] is missing')

get '/nested_optional_group', items: [{ key: 'foo', required_subitems: [{ value: 'bar' }] }]
expect(last_response.status).to eq(200)
expect(last_response.body).to eq('nested optional group works')

get '/nested_optional_group', items: [{ key: 'foo', required_subitems: [{ value: 'bar' }], optional_subitems: [{ not_value: 'baz' }] }]
expect(last_response.status).to eq(400)
expect(last_response.body).to eq('items[optional_subitems][value] is missing')
expect(last_response.body).to eq('items[0][optional_subitems][0][value] is missing')
end

it 'adds to declared parameters' do
Expand Down