Skip to content
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

Add validation to prevent arguments with both required and default_value #3011

Merged
merged 4 commits into from
Jul 21, 2020
Merged
Show file tree
Hide file tree
Changes from 2 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
11 changes: 11 additions & 0 deletions lib/graphql/schema/argument.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ def initialize(arg_name = nil, type_expr = nil, desc = nil, required:, type: nil
@from_resolver = from_resolver
@method_access = method_access

check_conflicting_params

Diego-Thomaz marked this conversation as resolved.
Show resolved Hide resolved
if definition_block
if definition_block.arity == 1
instance_exec(self, &definition_block)
Expand Down Expand Up @@ -186,6 +188,15 @@ def prepare_value(obj, value, context: nil)
raise "Invalid prepare for #{@owner.name}.name: #{@prepare.inspect}"
end
end

private

def check_conflicting_params
return unless !@null && default_value?

raise ArgumentError, "Argument '#{@name}' has conflicting params! " \
"Arguments can't be required and have default values at the same time!"
Diego-Thomaz marked this conversation as resolved.
Show resolved Hide resolved
end
end
end
end
61 changes: 38 additions & 23 deletions spec/graphql/schema/argument_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@ module SchemaArgumentTest
class Query < GraphQL::Schema::Object
field :field, String, null: true do
argument :arg, String, description: "test", required: false

argument :arg_with_block, String, required: false do
description "test"
end
argument :required_with_default_arg, Int, required: true, default_value: 1

argument :required_arg, Int, required: true
argument :default_value_arg, Int, required: false, default_value: 1
argument :aliased_arg, String, required: false, as: :renamed
argument :prepared_arg, Int, required: false, prepare: :multiply
argument :prepared_by_proc_arg, Int, required: false, prepare: ->(val, context) { context[:multiply_by] * val }
Expand Down Expand Up @@ -62,7 +63,8 @@ def self.object_from_id(id, ctx)

describe "#keys" do
it "is not overwritten by the 'keys' argument" do
expected_keys = ["aliasedArg", "arg", "argWithBlock", "explodingPreparedArg", "instrumentId", "instrumentIds", "keys", "preparedArg", "preparedByCallableArg", "preparedByProcArg", "requiredWithDefaultArg"]
expected_keys = ["aliasedArg", "arg", "argWithBlock", "defaultValueArg", "explodingPreparedArg", "instrumentId", "instrumentIds",
"keys", "preparedArg", "preparedByCallableArg", "preparedByProcArg", "requiredArg"]
assert_equal expected_keys, SchemaArgumentTest::Query.fields["field"].arguments.keys.sort
end
end
Expand Down Expand Up @@ -112,53 +114,53 @@ def self.object_from_id(id, ctx)
describe "as:" do
it "uses that Symbol for Ruby kwargs" do
query_str = <<-GRAPHQL
{ field(aliasedArg: "x") }
{ field(aliasedArg: "x", requiredArg: 1) }
GRAPHQL

res = SchemaArgumentTest::Schema.execute(query_str)
# Make sure it's getting the renamed symbol:
assert_equal '{:renamed=>"x", :required_with_default_arg=>1}', res["data"]["field"]
assert_equal '{:default_value_arg=>1, :renamed=>"x", :required_arg=>1}', res["data"]["field"]
end
end

describe "prepare:" do
it "calls the method on the field's owner" do
query_str = <<-GRAPHQL
{ field(preparedArg: 5) }
{ field(preparedArg: 5, requiredArg: 1) }
GRAPHQL

res = SchemaArgumentTest::Schema.execute(query_str, context: {multiply_by: 3})
# Make sure it's getting the renamed symbol:
assert_equal '{:prepared_arg=>15, :required_with_default_arg=>1}', res["data"]["field"]
assert_equal '{:default_value_arg=>1, :prepared_arg=>15, :required_arg=>1}', res["data"]["field"]
end

it "calls the method on the provided Proc" do
query_str = <<-GRAPHQL
{ field(preparedByProcArg: 5) }
{ field(preparedByProcArg: 5, requiredArg: 1) }
GRAPHQL

res = SchemaArgumentTest::Schema.execute(query_str, context: {multiply_by: 3})
# Make sure it's getting the renamed symbol:
assert_equal '{:prepared_by_proc_arg=>15, :required_with_default_arg=>1}', res["data"]["field"]
assert_equal '{:default_value_arg=>1, :prepared_by_proc_arg=>15, :required_arg=>1}', res["data"]["field"]
end

it "calls the method on the provided callable object" do
query_str = <<-GRAPHQL
{ field(preparedByCallableArg: 5) }
{ field(preparedByCallableArg: 5, requiredArg: 1) }
GRAPHQL

res = SchemaArgumentTest::Schema.execute(query_str, context: {multiply_by: 3})
# Make sure it's getting the renamed symbol:
assert_equal '{:prepared_by_callable_arg=>15, :required_with_default_arg=>1}', res["data"]["field"]
assert_equal '{:default_value_arg=>1, :prepared_by_callable_arg=>15, :required_arg=>1}', res["data"]["field"]
end

it "handles exceptions raised by prepare" do
query_str = <<-GRAPHQL
{ f1: field(arg: "echo"), f2: field(explodingPreparedArg: 5) }
{ f1: field(arg: "echo", requiredArg: 1), f2: field(explodingPreparedArg: 5, requiredArg: 1) }
GRAPHQL

res = SchemaArgumentTest::Schema.execute(query_str, context: {multiply_by: 3})
assert_equal({ 'f1' => '{:arg=>"echo", :required_with_default_arg=>1}', 'f2' => nil }, res['data'])
assert_equal({ 'f1' => '{:arg=>"echo", :default_value_arg=>1, :required_arg=>1}', 'f2' => nil }, res['data'])
assert_equal(res['errors'][0]['message'], 'boom!')
assert_equal(res['errors'][0]['path'], ['f2'])
end
Expand All @@ -167,47 +169,60 @@ def self.object_from_id(id, ctx)
describe "default_value:" do
it 'uses default_value: with no input' do
query_str = <<-GRAPHQL
{ field }
{ field(requiredArg: 1) }
GRAPHQL

res = SchemaArgumentTest::Schema.execute(query_str)
assert_equal '{:required_with_default_arg=>1}', res["data"]["field"]
assert_equal '{:default_value_arg=>1, :required_arg=>1}', res["data"]["field"]
end

it 'uses provided input value' do
query_str = <<-GRAPHQL
{ field(requiredWithDefaultArg: 2) }
{ field(defaultValueArg: 2, requiredArg: 1) }
GRAPHQL

res = SchemaArgumentTest::Schema.execute(query_str)
assert_equal '{:required_with_default_arg=>2}', res["data"]["field"]
assert_equal '{:default_value_arg=>2, :required_arg=>1}', res["data"]["field"]
end
end

describe 'required:' do
it 'respects non-null type' do
query_str = <<-GRAPHQL
{ field(requiredWithDefaultArg: null) }
{ field(requiredArg: null) }
GRAPHQL

res = SchemaArgumentTest::Schema.execute(query_str)
assert_equal "Argument 'requiredWithDefaultArg' on Field 'field' has an invalid value (null). Expected type 'Int!'.", res['errors'][0]['message']
assert_equal "Argument 'requiredArg' on Field 'field' has an invalid value (null). Expected type 'Int!'.", res['errors'][0]['message']
end
end

describe 'invalid:' do
it 'field has conflicting params' do
Diego-Thomaz marked this conversation as resolved.
Show resolved Hide resolved
exception = assert_raises ArgumentError do
SchemaArgumentTest::Query.fields["field"].argument(:bad_arg, Float, required: true, default_value: 1.0)
end

error_message = "Argument 'badArg' has conflicting params! Arguments can't be required and have default values at the same time!"
assert_equal(error_message, exception.message)
end
end

describe 'loads' do
it "loads input object arguments" do
query_str = <<-GRAPHQL
query { field(instrumentId: "Instrument/Drum Kit") }
query { field(instrumentId: "Instrument/Drum Kit", requiredArg: 1) }
GRAPHQL

res = SchemaArgumentTest::Schema.execute(query_str)
assert_equal "{:instrument=>#{Jazz::Models::Instrument.new("Drum Kit", "PERCUSSION").inspect}, :required_with_default_arg=>1}", res["data"]["field"]
assert_equal "{:default_value_arg=>1, :instrument=>#{Jazz::Models::Instrument.new("Drum Kit", "PERCUSSION").inspect}, :required_arg=>1}", res["data"]["field"]

query_str2 = <<-GRAPHQL
query { field(instrumentIds: ["Instrument/Organ"]) }
query { field(instrumentIds: ["Instrument/Organ"], requiredArg: 1) }
GRAPHQL

res = SchemaArgumentTest::Schema.execute(query_str2)
assert_equal "{:instruments=>[#{Jazz::Models::Instrument.new("Organ", "KEYS").inspect}], :required_with_default_arg=>1}", res["data"]["field"]
assert_equal "{:default_value_arg=>1, :instruments=>[#{Jazz::Models::Instrument.new("Organ", "KEYS").inspect}], :required_arg=>1}", res["data"]["field"]
end

it "returns nil when no ID is given and `required: false`" do
Expand Down