Skip to content

Commit 0f57e01

Browse files
Handle json array (#2538)
* Handle json array * Fix rubocop offenses * fix index * fix json array with nested json array * add change to changelog
1 parent 6e6958f commit 0f57e01

File tree

6 files changed

+183
-7
lines changed

6 files changed

+183
-7
lines changed

.rubocop_todo.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# This configuration was generated by
22
# `rubocop --auto-gen-config`
3-
# on 2025-02-08 13:42:40 UTC using RuboCop version 1.71.2.
3+
# on 2025-02-23 19:41:28 UTC using RuboCop version 1.71.2.
44
# The point is for the user to remove these configuration records
55
# one by one as the offenses are removed from the code base.
66
# Note that changes in the inspected code, or installation of new

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
#### Fixes
1212

13-
* Your contribution here.
13+
* [#2538](https://github.com/ruby-grape/grape/pull/2538): Fix validating nested json array params - [@mohammednasser-32](https://github.com/mohammednasser-32).
1414

1515
### 2.3.0 (2025-02-08)
1616

lib/grape/dsl/parameters.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ def map_params(params, element, is_array = false)
251251
# @return hash of parameters relevant for the current scope
252252
# @api private
253253
def params(params)
254-
params = @parent.params(params) if instance_variable_defined?(:@parent) && @parent
254+
params = @parent.params_meeting_dependency.presence || @parent.params(params) if instance_variable_defined?(:@parent) && @parent
255255
params = map_params(params, @element) if instance_variable_defined?(:@element) && @element
256256
params
257257
end

lib/grape/validations/params_scope.rb

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ module Grape
44
module Validations
55
class ParamsScope
66
attr_accessor :element, :parent, :index
7-
attr_reader :type
7+
attr_reader :type, :params_meeting_dependency
88

99
include Grape::DSL::Parameters
1010

@@ -67,6 +67,7 @@ def initialize(opts, &block)
6767
@type = opts[:type]
6868
@group = opts[:group]
6969
@dependent_on = opts[:dependent_on]
70+
@params_meeting_dependency = []
7071
@declared_params = []
7172
@index = nil
7273

@@ -94,7 +95,11 @@ def should_validate?(parameters)
9495
def meets_dependency?(params, request_params)
9596
return true unless @dependent_on
9697
return false if @parent.present? && !@parent.meets_dependency?(@parent.params(request_params), request_params)
97-
return params.any? { |param| meets_dependency?(param, request_params) } if params.is_a?(Array)
98+
99+
if params.is_a?(Array)
100+
@params_meeting_dependency = params.flatten.filter { |param| meets_dependency?(param, request_params) }
101+
return @params_meeting_dependency.present?
102+
end
98103

99104
meets_hash_dependency?(params)
100105
end
@@ -127,7 +132,7 @@ def meets_hash_dependency?(params)
127132
def full_name(name, index: nil)
128133
if nested?
129134
# Find our containing element's name, and append ours.
130-
"#{@parent.full_name(@element)}#{brackets(@index || index)}#{brackets(name)}"
135+
"#{@parent.full_name(@element)}#{brackets(index || @index)}#{brackets(name)}"
131136
elsif lateral?
132137
# Find the name of the element as if it was at the same nesting level
133138
# as our parent. We need to forward our index upward to achieve this.

spec/grape/dsl/parameters_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ def extract_message_option(attrs)
258258
it 'inherits params from parent' do
259259
parent_params = { foo: 'bar' }
260260
subject.parent = Object.new
261-
allow(subject.parent).to receive(:params).and_return(parent_params)
261+
allow(subject.parent).to receive_messages(params: parent_params, params_meeting_dependency: nil)
262262
expect(subject.params({})).to eq parent_params
263263
end
264264

spec/grape/validations/params_scope_spec.rb

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,177 @@ def initialize(value)
630630
expect(last_response.body).to eq 'inner3[0][baz][0][baz_category] is missing'
631631
end
632632

633+
context 'detect when json array' do
634+
before do
635+
subject.params do
636+
requires :array, type: Array do
637+
requires :a, type: String
638+
given a: ->(val) { val == 'a' } do
639+
requires :json, type: Hash do
640+
requires :b, type: String
641+
end
642+
end
643+
end
644+
end
645+
646+
subject.post '/nested_array' do
647+
'nested array works!'
648+
end
649+
end
650+
651+
it 'succeeds' do
652+
params = {
653+
array: [
654+
{
655+
a: 'a',
656+
json: { b: 'b' }
657+
},
658+
{
659+
a: 'b'
660+
}
661+
]
662+
}
663+
post '/nested_array', params.to_json, 'CONTENT_TYPE' => 'application/json'
664+
665+
expect(last_response.status).to eq(201)
666+
end
667+
668+
it 'fails' do
669+
params = {
670+
array: [
671+
{
672+
a: 'a',
673+
json: { b: 'b' }
674+
},
675+
{
676+
a: 'a'
677+
}
678+
]
679+
}
680+
post '/nested_array', params.to_json, 'CONTENT_TYPE' => 'application/json'
681+
682+
expect(last_response.status).to eq(400)
683+
expect(last_response.body).to eq('array[1][json] is missing, array[1][json][b] is missing')
684+
end
685+
end
686+
687+
context 'array without given' do
688+
before do
689+
subject.params do
690+
requires :array, type: Array do
691+
requires :a, type: Integer
692+
requires :b, type: Integer
693+
end
694+
end
695+
696+
subject.post '/array_without_given'
697+
end
698+
699+
it 'fails' do
700+
params = {
701+
array: [
702+
{
703+
a: 1,
704+
b: 2
705+
},
706+
{
707+
a: 3
708+
},
709+
{
710+
a: 5
711+
}
712+
]
713+
}
714+
post '/array_without_given', params.to_json, 'CONTENT_TYPE' => 'application/json'
715+
expect(last_response.body).to eq('array[1][b] is missing, array[2][b] is missing')
716+
expect(last_response.status).to eq(400)
717+
end
718+
end
719+
720+
context 'array with given' do
721+
before do
722+
subject.params do
723+
requires :array, type: Array do
724+
requires :a, type: Integer
725+
given a: lambda(&:odd?) do
726+
requires :b, type: Integer
727+
end
728+
end
729+
end
730+
731+
subject.post '/array_with_given'
732+
end
733+
734+
it 'fails' do
735+
params = {
736+
array: [
737+
{
738+
a: 1,
739+
b: 2
740+
},
741+
{
742+
a: 3
743+
},
744+
{
745+
a: 5
746+
}
747+
]
748+
}
749+
post '/array_with_given', params.to_json, 'CONTENT_TYPE' => 'application/json'
750+
expect(last_response.body).to eq('array[1][b] is missing, array[2][b] is missing')
751+
expect(last_response.status).to eq(400)
752+
end
753+
end
754+
755+
context 'nested json array with given' do
756+
before do
757+
subject.params do
758+
requires :workflow_nodes, type: Array do
759+
requires :steps, type: Array do
760+
requires :id, type: String
761+
optional :type, type: String, values: %w[send_messsge assign_user assign_team tag_conversation snooze close add_commit]
762+
given type: ->(val) { val == 'send_messsge' } do
763+
requires :message, type: Hash do
764+
requires :content, type: String
765+
end
766+
end
767+
end
768+
end
769+
end
770+
771+
subject.post '/nested_json_array_with_given'
772+
end
773+
774+
it 'passes' do
775+
params = {
776+
workflow_nodes: [
777+
{
778+
id: 'eqibmvEzPo8hQOSt',
779+
title: 'Node 1',
780+
is_start: true,
781+
steps: [
782+
{
783+
id: 'DvdSZaIm1hEd5XO5',
784+
type: 'send_messsge',
785+
message: {
786+
content: '打击好',
787+
menus: []
788+
}
789+
},
790+
{
791+
id: 'VY6MIwycBw0b51Ib',
792+
type: 'add_commit',
793+
comment_content: '初来乍到'
794+
}
795+
]
796+
}
797+
]
798+
}
799+
post '/nested_json_array_with_given', params.to_json, 'CONTENT_TYPE' => 'application/json'
800+
expect(last_response.status).to eq(201)
801+
end
802+
end
803+
633804
it 'includes the parameter within #declared(params)' do
634805
get '/test', a: true, b: true
635806

0 commit comments

Comments
 (0)