From 7c980cba95b5dba41dc6b269ec954ebce29f9975 Mon Sep 17 00:00:00 2001 From: Robert Mosolgo Date: Mon, 4 Jul 2022 06:56:53 -0400 Subject: [PATCH] Call prepare before loads for all arguments --- lib/graphql/schema/argument.rb | 20 +++++----- spec/graphql/schema/input_object_spec.rb | 48 +++++++++++++++++++++++- 2 files changed, 57 insertions(+), 11 deletions(-) diff --git a/lib/graphql/schema/argument.rb b/lib/graphql/schema/argument.rb index 7cee70f777..1268e53a8f 100644 --- a/lib/graphql/schema/argument.rb +++ b/lib/graphql/schema/argument.rb @@ -259,26 +259,26 @@ def coerce_into_values(parent_object, values, context, argument_values) # If this isn't lazy, then the block returns eagerly and assigns the result here # If it _is_ lazy, then we write the lazy to the hash, then update it later argument_values[arg_key] = context.schema.after_lazy(coerced_value) do |resolved_coerced_value| + owner.validate_directive_argument(self, resolved_coerced_value) + prepared_value = begin + prepare_value(parent_object, resolved_coerced_value, context: context) + rescue StandardError => err + context.schema.handle_or_reraise(context, err) + end + if loads && !from_resolver? loaded_value = begin - load_and_authorize_value(owner, coerced_value, context) + load_and_authorize_value(owner, prepared_value, context) rescue StandardError => err context.schema.handle_or_reraise(context, err) end end - maybe_loaded_value = loaded_value || resolved_coerced_value + maybe_loaded_value = loaded_value || prepared_value context.schema.after_lazy(maybe_loaded_value) do |resolved_loaded_value| - owner.validate_directive_argument(self, resolved_loaded_value) - prepared_value = begin - prepare_value(parent_object, resolved_loaded_value, context: context) - rescue StandardError => err - context.schema.handle_or_reraise(context, err) - end - # TODO code smell to access such a deeply-nested constant in a distant module argument_values[arg_key] = GraphQL::Execution::Interpreter::ArgumentValue.new( - value: prepared_value, + value: resolved_loaded_value, definition: self, default_used: default_used, ) diff --git a/spec/graphql/schema/input_object_spec.rb b/spec/graphql/schema/input_object_spec.rb index 9cd521b4e1..4dce01add8 100644 --- a/spec/graphql/schema/input_object_spec.rb +++ b/spec/graphql/schema/input_object_spec.rb @@ -126,6 +126,24 @@ def prep(val) end end + class Thing < GraphQL::Schema::Object + field :name, String + end + + class PreparedInputObj < GraphQL::Schema::InputObject + argument :thing_id, ID, loads: Thing, prepare: ->(val, ctx) { "thing-#{val}" } + end + + class ResolverPrepares < GraphQL::Schema::Resolver + argument :input_object, PreparedInputObj + argument :thing_id, ID, loads: Thing, prepare: ->(val, ctx) { "thing-#{val}" } + type [String], null: false + + def resolve(thing:, input_object:) + [thing.name, input_object[:thing].name] + end + end + class Query < GraphQL::Schema::Object field :inputs, [String], null: false do argument :input, InputObj @@ -134,6 +152,20 @@ class Query < GraphQL::Schema::Object def inputs(input:) [input.to_kwargs.inspect, input.instrument.name] end + + field :multiple_prepares, [String] do + argument :input_object, PreparedInputObj + argument :thing_id, ID, loads: Thing, prepare: ->(val, ctx) { "thing-#{val}" } + end + + def multiple_prepares(thing:, input_object:) + [thing.name, input_object[:thing].name] + end + + field :resolver_prepares, resolver: ResolverPrepares + + + field :t, Thing end class Mutation < GraphQL::Schema::Object @@ -193,7 +225,13 @@ class Schema < GraphQL::Schema lazy_resolve(Proc, :call) def self.object_from_id(id, ctx) - -> { Jazz::GloballyIdentifiableType.find(id) } + -> { + if id.start_with?("thing-") + OpenStruct.new(name: id) + else + Jazz::GloballyIdentifiableType.find(id) + end + } end def self.resolve_type(type, obj, ctx) @@ -205,6 +243,14 @@ def self.resolve_type(type, obj, ctx) end end + it "always prepares before loading" do + res = InputObjectPrepareTest::Schema.execute("{ resolverPrepares(thingId: \"abc\", inputObject: { thingId: \"def\" }) }") + assert_equal ["thing-abc", "thing-def"], res["data"]["resolverPrepares"] + + res = InputObjectPrepareTest::Schema.execute("{ multiplePrepares(thingId: \"abc\", inputObject: { thingId: \"def\" }) }") + assert_equal ["thing-abc", "thing-def"], res["data"]["multiplePrepares"] + end + it "calls methods on the input object" do query_str = <<-GRAPHQL { inputs(input: { a: 1, b: 2, c: 3, d: 4, e: 5, instrumentId: "Instrument/Drum Kit" }) }