Skip to content

Commit fc272ef

Browse files
authored
Merge pull request #5474 from rmosolgo/one-of-allow-all-hidden
Add allow_all_hidden, make it default for required: :nullable
2 parents 476930e + 73358f8 commit fc272ef

2 files changed

Lines changed: 111 additions & 5 deletions

File tree

lib/graphql/schema/validator/required_validator.rb

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,15 @@ class RequiredValidator < Validator
3838
# @param one_of [Array<Symbol>] A list of arguments, exactly one of which is required for this field
3939
# @param argument [Symbol] An argument that is required for this field
4040
# @param message [String]
41-
def initialize(one_of: nil, argument: nil, message: nil, **default_options)
41+
def initialize(one_of: nil, argument: nil, allow_all_hidden: nil, message: nil, **default_options)
4242
@one_of = if one_of
4343
one_of
4444
elsif argument
45-
[argument]
45+
[ argument ]
4646
else
4747
raise ArgumentError, "`one_of:` or `argument:` must be given in `validates required: {...}`"
4848
end
49+
@allow_all_hidden = allow_all_hidden.nil? ? !!argument : allow_all_hidden
4950
@message = message
5051
super(**default_options)
5152
end
@@ -54,10 +55,17 @@ def validate(_object, context, value)
5455
fully_matched_conditions = 0
5556
partially_matched_conditions = 0
5657

58+
visible_keywords = context.types.arguments(@validated).map(&:keyword)
59+
no_visible_conditions = true
60+
5761
if !value.nil?
5862
@one_of.each do |one_of_condition|
5963
case one_of_condition
6064
when Symbol
65+
if no_visible_conditions && visible_keywords.include?(one_of_condition)
66+
no_visible_conditions = false
67+
end
68+
6169
if value.key?(one_of_condition)
6270
fully_matched_conditions += 1
6371
end
@@ -66,6 +74,9 @@ def validate(_object, context, value)
6674
full_match = true
6775

6876
one_of_condition.each do |k|
77+
if no_visible_conditions && visible_keywords.include?(k)
78+
no_visible_conditions = false
79+
end
6980
if value.key?(k)
7081
any_match = true
7182
else
@@ -88,6 +99,18 @@ def validate(_object, context, value)
8899
end
89100
end
90101

102+
if no_visible_conditions
103+
if @allow_all_hidden
104+
return nil
105+
else
106+
raise GraphQL::Error, <<~ERR
107+
#{@validated.path} validates `required: ...` but all required arguments were hidden.
108+
109+
Update your schema definition to allow the client to see some fields or skip validation by adding `required: { ..., allow_all_hidden: true }`
110+
ERR
111+
end
112+
end
113+
91114
if fully_matched_conditions == 1 && partially_matched_conditions == 0
92115
nil # OK
93116
else

spec/graphql/schema/validator/required_validator_spec.rb

Lines changed: 86 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@
3333
]
3434
},
3535
{
36-
name: "All options hidden",
37-
config: { one_of: [:secret, :secret2] },
36+
name: "All options hidden, allow_all_hidden: true",
37+
config: { one_of: [:secret, :secret2], allow_all_hidden: true },
3838
cases: [
39-
{ query: "{ validated: multiValidated(a: 1, b: 2) }", result: nil, error_messages: ["multiValidated is missing a required argument."] },
39+
{ query: "{ validated: multiValidated(a: 1, b: 2) }", result: 3, error_messages: [] },
4040
],
4141
},
4242
{
@@ -92,4 +92,87 @@
9292
]
9393

9494
build_tests(:required, Integer, expectations)
95+
96+
97+
describe "when all arguments are hidden" do
98+
class RequiredHiddenSchema < GraphQL::Schema
99+
class BaseArgument < GraphQL::Schema::Argument
100+
def initialize(*args, always_hidden: false, **kwargs, &block)
101+
super(*args, **kwargs, &block)
102+
@always_hidden = always_hidden
103+
end
104+
105+
def visible?(ctx)
106+
!@always_hidden
107+
end
108+
end
109+
110+
class BaseField < GraphQL::Schema::Field
111+
argument_class(BaseArgument)
112+
end
113+
114+
class Query < GraphQL::Schema::Object
115+
field_class(BaseField)
116+
117+
field :one_argument, Int, fallback_value: 1 do
118+
argument :a, Int, required: :nullable, always_hidden: true
119+
end
120+
121+
field :two_arguments, Int, fallback_value: 2 do
122+
validates required: { one_of: [:a, :b], allow_all_hidden: true }
123+
argument :a, Int, required: false, always_hidden: true
124+
argument :b, Int, required: false, always_hidden: true
125+
end
126+
127+
field :two_arguments_error, Int, fallback_value: 2 do
128+
validates required: { one_of: [:a, :b] }
129+
argument :a, Int, required: false, always_hidden: true
130+
argument :b, Int, required: false, always_hidden: true
131+
end
132+
133+
field :three_arguments, Int, fallback_value: 3 do
134+
validates required: { one_of: [:a, :b], allow_all_hidden: true }
135+
argument :a, Int, required: false, always_hidden: true
136+
argument :b, Int, required: false, always_hidden: true
137+
argument :c, Int
138+
end
139+
140+
field :four_arguments, Int, fallback_value: 4 do
141+
validates required: { one_of: [[:a, :b], :c, :d], allow_all_hidden: true}
142+
argument :a, Int, required: false, always_hidden: true
143+
argument :b, Int, required: false, always_hidden: true
144+
argument :c, Int, required: false, always_hidden: true
145+
argument :d, Int, required: false, always_hidden: true
146+
end
147+
end
148+
149+
query(Query)
150+
use GraphQL::Schema::Visibility
151+
end
152+
153+
it "Doesn't require any of one_of to be present" do
154+
result = RequiredHiddenSchema.execute("{ threeArguments(c: 5) }")
155+
assert_equal 3, result["data"]["threeArguments"]
156+
157+
result = RequiredHiddenSchema.execute("{ twoArguments }")
158+
assert_equal 2, result["data"]["twoArguments"]
159+
160+
err = assert_raises GraphQL::Error do
161+
RequiredHiddenSchema.execute("{ twoArgumentsError }")
162+
end
163+
164+
expected_message = "Query.twoArgumentsError validates `required: ...` but all required arguments were hidden.\n\nUpdate your schema definition to allow the client to see some fields or skip validation by adding `required: { ..., allow_all_hidden: true }`\n"
165+
assert_equal expected_message, err.message
166+
end
167+
168+
it "doesn't require hidden arguments when required as a group" do
169+
result = RequiredHiddenSchema.execute("{ fourArguments }")
170+
assert_equal 4, result["data"]["fourArguments"]
171+
end
172+
173+
it "Doesn't require hidden argument to be present" do
174+
result = RequiredHiddenSchema.execute("{ oneArgument }")
175+
assert_equal 1, result["data"]["oneArgument"]
176+
end
177+
end
95178
end

0 commit comments

Comments
 (0)