diff --git a/lib/graphql/execution/input_values.rb b/lib/graphql/execution/input_values.rb index ca6438cd57..133fa0bf91 100644 --- a/lib/graphql/execution/input_values.rb +++ b/lib/graphql/execution/input_values.rb @@ -45,10 +45,12 @@ def argument_values(owner_defn, argument_nodes, field_resolve_step) arg_node = argument_nodes.find { |a| a.name == arg_graphql_key } if arg_node.nil? || (arg_node.value.is_a?(Language::Nodes::VariableIdentifier) && !variable_values.key?(arg_node.value.name)) if argument_definition.default_value? - argument_value(argument_values, arg_ruby_key, argument_definition, argument_definition.default_value, nil, field_resolve_step) + arg_value = value_from_ast(argument_definition.default_value, argument_definition.type) + argument_value(argument_values, arg_ruby_key, argument_definition, arg_value, nil, field_resolve_step) end else - argument_value(argument_values, arg_ruby_key, argument_definition, arg_node.value, nil, field_resolve_step) + arg_value = value_from_ast(arg_node.value, argument_definition.type) + argument_value(argument_values, arg_ruby_key, argument_definition, arg_value, nil, field_resolve_step) end end @@ -110,8 +112,6 @@ def argument_value(argument_values, argument_key, argument_definition, arg_value treat_as_type = treat_as_type.of_type end - arg_value = value_from_ast(arg_value, treat_as_type) - if treat_as_type.kind.list? && !arg_value.nil? inner_t = treat_as_type.unwrap arg_value = if arg_value.is_a?(Array) @@ -125,14 +125,6 @@ def argument_value(argument_values, argument_key, argument_definition, arg_value end end - if override_type.nil? # only on root arguments, not list elements - arg_value = begin - argument_definition.prepare_value(nil, arg_value, context: @query.context) - rescue StandardError => err - @runner.schema.handle_or_reraise(@query.context, err) - end - end - if arg_value && treat_as_type.kind.input_object? arg_defns = @query.types.arguments(treat_as_type) new_arg_value = {} @@ -151,7 +143,15 @@ def argument_value(argument_values, argument_key, argument_definition, arg_value end end end - arg_value = new_arg_value + arg_value = treat_as_type.new(nil, ruby_kwargs: new_arg_value, context: @query.context, defaults_used: nil) + end + + if override_type.nil? # only on root arguments, not list elements + arg_value = begin + argument_definition.prepare_value(nil, arg_value, context: @query.context) + rescue StandardError => err + @runner.schema.handle_or_reraise(@query.context, err) + end end if field_resolve_step && arg_value && override_type.nil? && argument_definition.loads diff --git a/lib/graphql/schema/argument.rb b/lib/graphql/schema/argument.rb index eb0879ee24..239ce4dc51 100644 --- a/lib/graphql/schema/argument.rb +++ b/lib/graphql/schema/argument.rb @@ -426,9 +426,6 @@ def recursively_prepare_input_object(value, type, context) elsif value.is_a?(GraphQL::Schema::InputObject) value.validate_for(context) value.prepare - elsif value.is_a?(Hash) - type.validate_for(value, context) - value else value end diff --git a/lib/graphql/schema/input_object.rb b/lib/graphql/schema/input_object.rb index 7af9b3cecd..05420201b4 100644 --- a/lib/graphql/schema/input_object.rb +++ b/lib/graphql/schema/input_object.rb @@ -112,12 +112,6 @@ def validate_for(context) nil end - # @api private - def self.validate_for(ruby_style_hash, context) - # Pass this object's class with `as` so that messages are rendered correctly from inherited validators - Schema::Validator.validate!(validators, nil, context, ruby_style_hash, as: self) - nil - end class << self def authorized?(obj, value, ctx) # Authorize each argument (but this doesn't apply if `prepare` is implemented): diff --git a/spec/graphql/execution/input_values_spec.rb b/spec/graphql/execution/input_values_spec.rb index 7b26cf9281..6abbb98c66 100644 --- a/spec/graphql/execution/input_values_spec.rb +++ b/spec/graphql/execution/input_values_spec.rb @@ -2,37 +2,41 @@ require "spec_helper" class ExecutionInputValuesTest < Minitest::Test - TEST_SCHEMA = GraphQL::Schema.from_definition(%| - enum TestStatus { - ACTIVE - INACTIVE - } + class TestSchema < GraphQL::Schema + class TestStatus < GraphQL::Schema::Enum + value :ACTIVE + value :INACTIVE + end - input TestInput { - string: String - float: Float - int: Int - enum: TestStatus - } + class TestInput < GraphQL::Schema::InputObject + argument :string, String, required: false + argument :float, Float, required: false + argument :int, Int, required: false + argument :enum, TestStatus, required: false + end - type Mutation { - testInput(input: TestInput): Boolean - } + class Mutation < GraphQL::Schema::Object + field :test_input, Boolean do + argument :input, TestInput, required: false + end - type Query { - ping: Boolean - } - |) + field :test_list_input, Boolean do + argument :input, [TestInput, null: true], required: false + end + end + mutation(Mutation) + query(Mutation) # Just to have something + end class DummyRunner def add_step(s); end - def schema; TEST_SCHEMA; end + def schema; TestSchema; end end - def get_input_values(variables_string: nil, variables: nil) - query_str = "query#{variables_string ? "(#{variables_string})" : ""} { __typename }" - query = GraphQL::Query.new(TEST_SCHEMA, query_str, validate: false, variables: variables) + def get_input_values(query_string: nil, variables_string: nil, variables: nil) + query_string ||= "query#{variables_string ? "(#{variables_string})" : ""} { __typename }" + query = GraphQL::Query.new(TestSchema, query_string, validate: false, variables: variables) GraphQL::Execution::InputValues.new(query, DummyRunner.new) end @@ -67,6 +71,38 @@ def test_it_produces_argument_values_for_simple_scalars def test_it_produces_argument_values_for_input_objects input = get_input_values - assert_equal({input: { string: "a", enum: "ACTIVE" } }, input.argument_values(TEST_SCHEMA.find("Mutation.testInput"), get_argument_nodes("input: { string: \"a\", enum: ACTIVE }"), nil)) + assert_equal_input( {input: { string: "a", enum: "ACTIVE" } }, input.argument_values(TestSchema.find("Mutation.testInput"), get_argument_nodes("input: { string: \"a\", enum: ACTIVE }"), nil)) + end + + def assert_equal_input(expected_ruby_hash, graphql_input, path = []) + case expected_ruby_hash + when Array + assert_instance_of Array, graphql_input, "Matches at `#{path.join(".")}`" + expected_ruby_hash.each_with_index do |next_expected, idx| + assert_equal_input(next_expected, graphql_input[idx], path + [idx]) + end + when Hash + if path.empty? + assert_instance_of Hash, graphql_input, "Matches at `#{path.join(".")}`" + else + assert_kind_of GraphQL::Schema::InputObject, graphql_input, "Matches at `#{path.join(".")}`" + graphql_input = graphql_input.to_h + end + expected_ruby_hash.each do |k, v| + assert_equal_input(v, graphql_input[k], path + [k]) + end + else + assert_equal expected_ruby_hash, graphql_input, "Matches at `#{path.join(".")}`" + end + end + + def test_it_works_with_arrays_of_input_objects + input = get_input_values(variables_string: "$string: String = \"abc\", $string2: String, $input: TestInput!", variables: { string2: "xyz", input: { string: "nested" }}) + assert_equal_input({input: [{}]}, input.argument_values(TestSchema.find("Mutation.testListInput"), get_argument_nodes("input: { string: $s }"), nil)) + assert_equal_input({input: [{ string: "Str" }]}, input.argument_values(TestSchema.find("Mutation.testListInput"), get_argument_nodes("input: { string: \"Str\" }"), nil)) + assert_equal_input({input: [{ string: "abc" }]}, input.argument_values(TestSchema.find("Mutation.testListInput"), get_argument_nodes("input: { string: $string }"), nil)) + assert_equal_input({input: [{ string: "xyz" }]}, input.argument_values(TestSchema.find("Mutation.testListInput"), get_argument_nodes("input: { string: $string2 }"), nil)) + assert_equal_input({input: [{ string: "nested" }]}, input.argument_values(TestSchema.find("Mutation.testListInput"), get_argument_nodes("input: $input"), nil)) + assert_equal_input({input: [{}, {string: "Str"}, {string: "abc"}, {string: "xyz"}, {string: "nested"}]}, input.argument_values(TestSchema.find("Mutation.testListInput"), get_argument_nodes("input: [{string: $s}, {string: \"Str\"}, {string: $string }, { string: $string2 }, $input]"), nil)) end end