Skip to content

Commit

Permalink
Make sure input objects are only prepared once; handle prepared input…
Browse files Browse the repository at this point in the history
… objects in FieldUsage analyzer
  • Loading branch information
rmosolgo committed Apr 5, 2024
1 parent fad460d commit 7fc0a5d
Show file tree
Hide file tree
Showing 5 changed files with 38 additions and 8 deletions.
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
self.new(resolved_arguments, ruby_kwargs: resolved_arguments.keyword_arguments, context: ctx, defaults_used: nil)
end
end
end
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

0 comments on commit 7fc0a5d

Please sign in to comment.