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

Fix field usage on prepared input types with original value #4902

Merged
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 7 additions & 5 deletions lib/graphql/analysis/ast/field_usage.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,26 +41,28 @@ def extract_deprecated_arguments(argument_values)
@used_deprecated_arguments << argument.definition.path
end

next if argument.value.nil?
arg_val = argument.value

next if arg_val.nil?

argument_type = argument.definition.type
if argument_type.non_null?
argument_type = argument_type.of_type
end

if argument_type.kind.input_object?
extract_deprecated_arguments(argument.value.arguments.argument_values) # rubocop:disable Development/ContextIsPassedCop -- runtime args instance
extract_deprecated_arguments(argument.original_value.arguments.argument_values) # rubocop:disable Development/ContextIsPassedCop -- runtime args instance
elsif argument_type.kind.enum?
extract_deprecated_enum_value(argument_type, argument.value)
extract_deprecated_enum_value(argument_type, arg_val)
elsif argument_type.list?
inner_type = argument_type.unwrap
case inner_type.kind
when TypeKinds::INPUT_OBJECT
argument.value.each do |value|
argument.original_value.each do |value|
extract_deprecated_arguments(value.arguments.argument_values) # rubocop:disable Development/ContextIsPassedCop -- runtime args instance
end
when TypeKinds::ENUM
argument.value.each do |value|
arg_val.each do |value|
extract_deprecated_enum_value(inner_type, value)
end
else
Expand Down
6 changes: 5 additions & 1 deletion lib/graphql/execution/interpreter/argument_value.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,19 @@ class Interpreter
# A container for metadata regarding arguments present in a GraphQL query.
# @see Interpreter::Arguments#argument_values for a hash of these objects.
class ArgumentValue
def initialize(definition:, value:, default_used:)
def initialize(definition:, value:, original_value:, default_used:)
@definition = definition
@value = value
@original_value = original_value
@default_used = default_used
end

# @return [Object] The Ruby-ready value for this Argument
attr_reader :value

# @return [Object] The value of this argument _before_ `prepare` is applied.
attr_reader :original_value

# @return [GraphQL::Schema::Argument] The definition instance for this argument
attr_reader :definition

Expand Down
1 change: 1 addition & 0 deletions lib/graphql/schema/argument.rb
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ def coerce_into_values(parent_object, values, context, argument_values)
# TODO code smell to access such a deeply-nested constant in a distant module
argument_values[arg_key] = GraphQL::Execution::Interpreter::ArgumentValue.new(
value: resolved_loaded_value,
original_value: resolved_coerced_value,
definition: self,
default_used: default_used,
)
Expand Down
3 changes: 1 addition & 2 deletions lib/graphql/schema/input_object.rb
Original file line number Diff line number Diff line change
Expand Up @@ -215,8 +215,7 @@ def coerce_input(value, ctx)
if resolved_arguments.is_a?(GraphQL::Error)
raise resolved_arguments
else
input_obj_instance = self.new(resolved_arguments, ruby_kwargs: resolved_arguments.keyword_arguments, context: ctx, defaults_used: nil)
input_obj_instance.prepare
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @rmosolgo, we tried upgrading to the latest version of graphql ruby and we are seeing an error where input types aren't having their prepare method called. For example, one of our input types looks like this:

class CreditNoteLineItemInput < Types::BaseInputObject

    argument :amount, Types::MoneyInput, required: true, description: "The amount to credit."
    argument :charge_id,
             ID,
             required: true,
             loads: Types::Charge,
             as: :charge,
             description: "The charge being credited."

    def prepare
      { payable: charge, amount: amount }
    end
  end

When our unit tests run, input arguments for this type aren't being provided as a hash. I haven't had time to prepare a test case, but wondering if you may have introduced a bug on this PR?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

may have introduced a bug on this PR?

Always possible! Perhaps related: #4932

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought this bug might be related to both loads: and def prepare being used, but it's not that simple. I added a spec in c72f700 and it passed.

I'd like to track this down! Could you please open an issue with some details:

  • The source for the input object (shared above)
  • An example query which replicates the bug
  • The field where the input object is used
  • Anything else you think might be relevant

Thanks!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When our unit tests run

How do your unit tests look like? Is this a unit test for the input object?

At GitLab, we had to update our unit tests because we were testing by calling coerce_isolated_input. The #prepare now happens outside of this method so we had to call #prepare manually. See https://gitlab.com/gitlab-org/gitlab/-/commit/4dc3e1866fc89387f1a7de004124751e0bda59fb

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am taking another look and I think this problem may be related to some related libraries that we use at Shopify. I pulled the latest versions of all the Graphql gems we use and I am no longer seeing this error. Will report back and open an issue if I have anything definitive. Apologies for the false alarm.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries, please open a new issue if you find anything that we should investigate further 👍

self.new(resolved_arguments, ruby_kwargs: resolved_arguments.keyword_arguments, context: ctx, defaults_used: nil)
end
end
end
Expand Down
11 changes: 11 additions & 0 deletions spec/graphql/analysis/ast/field_usage_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,17 @@
end
end

describe "mutation with deprecated arguments with prepared values" do
let(:query_string) {%|
mutation {
pushValue(preparedTestInput: { deprecatedDate: "2020-10-10" })
}
|}

it "keeps track of nested deprecated arguments" do
assert_equal ['PreparedDateInput.deprecatedDate'], result[:used_deprecated_arguments]
end
end

describe "when an argument prepare raises a GraphQL::ExecutionError" do
class ArgumentErrorFieldUsageSchema < GraphQL::Schema
Expand Down
24 changes: 24 additions & 0 deletions spec/graphql/schema/input_object_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,17 @@ def prepare
end
end

class OnlyOnePrepareInputObject < GraphQL::Schema::InputObject
argument :i, Int

attr_reader :prepared_count
def prepare
@prepared_count ||= 0
@prepared_count += 1
super
end
end

class SmallIntegerArgument < GraphQL::Schema::Argument
def authorized?(obj, val, ctx)
if val > 100
Expand Down Expand Up @@ -388,6 +399,14 @@ def inputs(input:)
end

field :hash_input, resolver: HashInputResolver

field :prepare_once, Int do
argument :input, OnlyOnePrepareInputObject
end

def prepare_once(input:)
input.prepared_count
end
end

class Schema < GraphQL::Schema
Expand All @@ -404,6 +423,11 @@ class Schema < GraphQL::Schema
assert_equal "5..10", res["data"]["inputs"]
end

it "only prepares once" do
res = InputObjectPrepareObjectTest::Schema.execute("{ prepareOnce( input: { i: 1 } ) }")
assert_equal 1, res["data"]["prepareOnce"]
end

it "calls prepare on the input object (variable)" do
query_str = <<-GRAPHQL
query ($input: RangeInput!){ inputs(input: $input) }
Expand Down
13 changes: 13 additions & 0 deletions spec/support/dummy/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,18 @@ class DairyProductInput < BaseInputObject
argument :old_source, String, required: false, deprecation_reason: "No longer supported"
end

class PreparedDateInput < BaseInputObject
description "Input with prepared value"
argument :date, String, description: "date as a string", required: false
argument :deprecated_date, String, description: "date as a string", required: false, deprecation_reason: "Use date"

def prepare
return nil unless date || deprecated_date

Date.parse(date || deprecated_date)
end
end

class DeepNonNull < BaseObject
field :non_null_int, Integer, null: false do
argument :returning, Integer, required: false
Expand Down Expand Up @@ -492,6 +504,7 @@ class DairyAppMutation < BaseObject
field :push_value, [Integer], null: false, description: "Push a value onto a global array :D" do
argument :value, Integer, as: :val
argument :deprecated_test_input, DairyProductInput, required: false
argument :prepared_test_input, PreparedDateInput, required: false
end
def push_value(val:)
GLOBAL_VALUES << val
Expand Down