graphql 1.12.6 → 1.12.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of graphql might be problematic. Click here for more details.

Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/install_generator.rb +1 -1
  3. data/lib/generators/graphql/templates/graphql_controller.erb +2 -2
  4. data/lib/graphql.rb +10 -10
  5. data/lib/graphql/backtrace/table.rb +14 -2
  6. data/lib/graphql/dataloader.rb +44 -15
  7. data/lib/graphql/execution/errors.rb +109 -11
  8. data/lib/graphql/execution/execute.rb +1 -1
  9. data/lib/graphql/execution/interpreter.rb +4 -8
  10. data/lib/graphql/execution/interpreter/runtime.rb +207 -188
  11. data/lib/graphql/introspection.rb +1 -1
  12. data/lib/graphql/introspection/directive_type.rb +7 -3
  13. data/lib/graphql/introspection/schema_type.rb +1 -1
  14. data/lib/graphql/pagination/active_record_relation_connection.rb +7 -0
  15. data/lib/graphql/pagination/connections.rb +1 -1
  16. data/lib/graphql/pagination/relation_connection.rb +8 -1
  17. data/lib/graphql/query.rb +1 -3
  18. data/lib/graphql/query/null_context.rb +7 -1
  19. data/lib/graphql/query/validation_pipeline.rb +1 -1
  20. data/lib/graphql/rake_task.rb +3 -0
  21. data/lib/graphql/schema.rb +49 -237
  22. data/lib/graphql/schema/addition.rb +238 -0
  23. data/lib/graphql/schema/argument.rb +55 -36
  24. data/lib/graphql/schema/directive/transform.rb +13 -1
  25. data/lib/graphql/schema/input_object.rb +2 -2
  26. data/lib/graphql/schema/loader.rb +8 -0
  27. data/lib/graphql/schema/member/base_dsl_methods.rb +3 -15
  28. data/lib/graphql/schema/object.rb +19 -5
  29. data/lib/graphql/schema/resolver.rb +46 -24
  30. data/lib/graphql/schema/scalar.rb +3 -1
  31. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +3 -1
  32. data/lib/graphql/static_validation/rules/argument_literals_are_compatible_error.rb +6 -2
  33. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +2 -1
  34. data/lib/graphql/static_validation/rules/arguments_are_defined_error.rb +4 -2
  35. data/lib/graphql/static_validation/rules/directives_are_defined.rb +1 -1
  36. data/lib/graphql/static_validation/rules/fields_will_merge.rb +17 -8
  37. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +1 -1
  38. data/lib/graphql/static_validation/validator.rb +5 -0
  39. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +4 -3
  40. data/lib/graphql/subscriptions/broadcast_analyzer.rb +0 -3
  41. data/lib/graphql/subscriptions/serialize.rb +11 -1
  42. data/lib/graphql/types/relay/base_connection.rb +4 -0
  43. data/lib/graphql/types/relay/connection_behaviors.rb +21 -10
  44. data/lib/graphql/types/relay/edge_behaviors.rb +12 -1
  45. data/lib/graphql/version.rb +1 -1
  46. metadata +3 -3
  47. data/lib/graphql/execution/interpreter/hash_response.rb +0 -46
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ebd2e71516a89475e1617ee60250d4d88f6d3743090ae8010e6918ce425a71fc
4
- data.tar.gz: c0577dd5aac0765abf748f9ba86b3a3a814658abba7a2285b4e0d929959de50f
3
+ metadata.gz: 9e46a535c108ff0ae582fcfa598b21cda8faad15142629b1cdae772f3d24cd7e
4
+ data.tar.gz: 3185ec249d624c6b23920d9d9fb47167ac031b7645053ff705e3c04acc0fa571
5
5
  SHA512:
6
- metadata.gz: 832d9464fe69bacc19d4a8715e5d2869d8ff15a4c1c34316d9c092b295e2175ac4f7a746a736773914ac4789478baa28a1f451c57cee732346901d5e4e10e430
7
- data.tar.gz: 84f4918a0473bf07c657ca39392866ece0a36ca592272f85580417882b762e7210cd6404ee1671ad651ac7608acdb0103a4a262253e48ccc9b75be2804c5caf4
6
+ metadata.gz: 989c1074a6de63a31002d2bd92da719e1b08682d3eefe361add671a75c1991173b13cc6516f8ede8ff5bcf7499935e514cd8d7d57a2940e85160bc427538e99c
7
+ data.tar.gz: cb40ba585e721d28856951312755ef84bdd698a7299b1a383dd4f9db77d8b137e8d21ee9a8784b14780b4230a5a5325520d0ceadf90f6bd055781cc7159257b5
@@ -122,7 +122,7 @@ module Graphql
122
122
  if options.api?
123
123
  say("Skipped graphiql, as this rails project is API only")
124
124
  say(" You may wish to use GraphiQL.app for development: https://github.com/skevy/graphiql-app")
125
- elsif !options[:skip_graphiql]
125
+ elsif !options[:skip_graphiql] && !File.read(Rails.root.join("Gemfile")).include?("graphiql-rails")
126
126
  gem("graphiql-rails", group: :development)
127
127
 
128
128
  # This is a little cheat just to get cleaner shell output:
@@ -15,9 +15,9 @@ class GraphqlController < ApplicationController
15
15
  }
16
16
  result = <%= schema_name %>.execute(query, variables: variables, context: context, operation_name: operation_name)
17
17
  render json: result
18
- rescue => e
18
+ rescue StandardError => e
19
19
  raise e unless Rails.env.development?
20
- handle_error_in_development e
20
+ handle_error_in_development(e)
21
21
  end
22
22
 
23
23
  private
data/lib/graphql.rb CHANGED
@@ -81,10 +81,19 @@ end
81
81
  # Order matters for these:
82
82
 
83
83
  require "graphql/execution_error"
84
+ require "graphql/runtime_type_error"
85
+ require "graphql/unresolved_type_error"
86
+ require "graphql/invalid_null_error"
87
+ require "graphql/analysis_error"
88
+ require "graphql/coercion_error"
89
+ require "graphql/invalid_name_error"
90
+ require "graphql/integer_decoding_error"
91
+ require "graphql/integer_encoding_error"
92
+ require "graphql/string_encoding_error"
93
+
84
94
  require "graphql/define"
85
95
  require "graphql/base_type"
86
96
  require "graphql/object_type"
87
-
88
97
  require "graphql/enum_type"
89
98
  require "graphql/input_object_type"
90
99
  require "graphql/interface_type"
@@ -109,9 +118,6 @@ require "graphql/analysis"
109
118
  require "graphql/tracing"
110
119
  require "graphql/dig"
111
120
  require "graphql/execution"
112
- require "graphql/runtime_type_error"
113
- require "graphql/unresolved_type_error"
114
- require "graphql/invalid_null_error"
115
121
  require "graphql/pagination"
116
122
  require "graphql/schema"
117
123
  require "graphql/query"
@@ -133,12 +139,6 @@ require "graphql/static_validation"
133
139
  require "graphql/dataloader"
134
140
  require "graphql/introspection"
135
141
 
136
- require "graphql/analysis_error"
137
- require "graphql/coercion_error"
138
- require "graphql/invalid_name_error"
139
- require "graphql/integer_decoding_error"
140
- require "graphql/integer_encoding_error"
141
- require "graphql/string_encoding_error"
142
142
  require "graphql/version"
143
143
  require "graphql/compatibility"
144
144
  require "graphql/function"
@@ -83,7 +83,7 @@ module GraphQL
83
83
  value = if top && @override_value
84
84
  @override_value
85
85
  else
86
- @context.query.context.namespace(:interpreter)[:runtime].value_at(context_entry.path)
86
+ value_at(@context.query.context.namespace(:interpreter)[:runtime], context_entry.path)
87
87
  end
88
88
  rows << [
89
89
  "#{context_entry.ast_node ? context_entry.ast_node.position.join(":") : ""}",
@@ -130,7 +130,7 @@ module GraphQL
130
130
  if object.is_a?(GraphQL::Schema::Object)
131
131
  object = object.object
132
132
  end
133
- value = context_entry.namespace(:interpreter)[:runtime].value_at([])
133
+ value = value_at(context_entry.namespace(:interpreter)[:runtime], [])
134
134
  rows << [
135
135
  "#{position}",
136
136
  "#{op_type}#{op_name ? " #{op_name}" : ""}",
@@ -142,6 +142,18 @@ module GraphQL
142
142
  raise "Unexpected get_rows subject #{context_entry.class} (#{context_entry.inspect})"
143
143
  end
144
144
  end
145
+
146
+ def value_at(runtime, path)
147
+ response = runtime.response
148
+ path.each do |key|
149
+ if response && (response = response[key])
150
+ next
151
+ else
152
+ break
153
+ end
154
+ end
155
+ response
156
+ end
145
157
  end
146
158
  end
147
159
  end
@@ -29,7 +29,12 @@ module GraphQL
29
29
 
30
30
  def initialize
31
31
  @source_cache = Hash.new { |h, source_class| h[source_class] = Hash.new { |h2, batch_parameters|
32
- source = source_class.new(*batch_parameters)
32
+ source = if RUBY_VERSION < "3"
33
+ source_class.new(*batch_parameters)
34
+ else
35
+ batch_args, batch_kwargs = batch_parameters
36
+ source_class.new(*batch_args, **batch_kwargs)
37
+ end
33
38
  source.setup(self)
34
39
  h2[batch_parameters] = source
35
40
  }
@@ -43,8 +48,15 @@ module GraphQL
43
48
  # @param batch_parameters [Array<Object>]
44
49
  # @return [GraphQL::Dataloader::Source] An instance of {source_class}, initialized with `self, *batch_parameters`,
45
50
  # and cached for the lifetime of this {Multiplex}.
46
- def with(source_class, *batch_parameters)
47
- @source_cache[source_class][batch_parameters]
51
+ if RUBY_VERSION < "3"
52
+ def with(source_class, *batch_parameters)
53
+ @source_cache[source_class][batch_parameters]
54
+ end
55
+ else
56
+ def with(source_class, *batch_args, **batch_kwargs)
57
+ batch_parameters = [batch_args, batch_kwargs]
58
+ @source_cache[source_class][batch_parameters]
59
+ end
48
60
  end
49
61
 
50
62
  # Tell the dataloader that this fiber is waiting for data.
@@ -104,7 +116,7 @@ module GraphQL
104
116
  while @pending_jobs.any?
105
117
  # Create a Fiber to consume jobs until one of the jobs yields
106
118
  # or jobs run out
107
- f = Fiber.new {
119
+ f = spawn_fiber {
108
120
  while (job = @pending_jobs.shift)
109
121
  job.call
110
122
  end
@@ -124,26 +136,24 @@ module GraphQL
124
136
  # This is where an evented approach would be even better -- can we tell which
125
137
  # fibers are ready to continue, and continue execution there?
126
138
  #
127
- source_fiber_stack = if (first_source_fiber = create_source_fiber)
139
+ source_fiber_queue = if (first_source_fiber = create_source_fiber)
128
140
  [first_source_fiber]
129
141
  else
130
142
  nil
131
143
  end
132
144
 
133
- if source_fiber_stack
134
- # Use a stack with `.pop` here so that when a source causes another source to become pending,
135
- # that newly-pending source will run _before_ the one that depends on it.
136
- # (See below where the old fiber is pushed to the stack, then the new fiber is pushed on the stack.)
137
- while (outer_source_fiber = source_fiber_stack.pop)
145
+ if source_fiber_queue
146
+ while (outer_source_fiber = source_fiber_queue.shift)
138
147
  resume(outer_source_fiber)
139
148
 
140
- if outer_source_fiber.alive?
141
- source_fiber_stack << outer_source_fiber
142
- end
143
149
  # If this source caused more sources to become pending, run those before running this one again:
144
150
  next_source_fiber = create_source_fiber
145
151
  if next_source_fiber
146
- source_fiber_stack << next_source_fiber
152
+ source_fiber_queue << next_source_fiber
153
+ end
154
+
155
+ if outer_source_fiber.alive?
156
+ source_fiber_queue << outer_source_fiber
147
157
  end
148
158
  end
149
159
  end
@@ -191,7 +201,7 @@ module GraphQL
191
201
  #
192
202
  # This design could probably be improved by maintaining a `@pending_sources` queue which is shared by the fibers,
193
203
  # similar to `@pending_jobs`. That way, when a fiber is resumed, it would never pick up work that was finished by a different fiber.
194
- source_fiber = Fiber.new do
204
+ source_fiber = spawn_fiber do
195
205
  pending_sources.each(&:run_pending_keys)
196
206
  end
197
207
  end
@@ -204,5 +214,24 @@ module GraphQL
204
214
  rescue UncaughtThrowError => e
205
215
  throw e.tag, e.value
206
216
  end
217
+
218
+ # Copies the thread local vars into the fiber thread local vars. Many
219
+ # gems (such as RequestStore, MiniRacer, etc.) rely on thread local vars
220
+ # to keep track of execution context, and without this they do not
221
+ # behave as expected.
222
+ #
223
+ # @see https://github.com/rmosolgo/graphql-ruby/issues/3449
224
+ def spawn_fiber
225
+ fiber_locals = {}
226
+
227
+ Thread.current.keys.each do |fiber_var_key|
228
+ fiber_locals[fiber_var_key] = Thread.current[fiber_var_key]
229
+ end
230
+
231
+ Fiber.new do
232
+ fiber_locals.each { |k, v| Thread.current[k] = v }
233
+ yield
234
+ end
235
+ end
207
236
  end
208
237
  end
@@ -18,21 +18,83 @@ module GraphQL
18
18
  #
19
19
  class Errors
20
20
  def self.use(schema)
21
- if schema.plugins.any? { |(plugin, kwargs)| plugin == self }
22
- definition_line = caller(2, 1).first
23
- GraphQL::Deprecation.warn("GraphQL::Execution::Errors is now installed by default, remove `use GraphQL::Execution::Errors` from #{definition_line}")
24
- end
25
- schema.error_handler = self.new(schema)
21
+ definition_line = caller(2, 1).first
22
+ GraphQL::Deprecation.warn("GraphQL::Execution::Errors is now installed by default, remove `use GraphQL::Execution::Errors` from #{definition_line}")
26
23
  end
27
24
 
25
+ NEW_HANDLER_HASH = ->(h, k) {
26
+ h[k] = {
27
+ class: k,
28
+ handler: nil,
29
+ subclass_handlers: Hash.new(&NEW_HANDLER_HASH),
30
+ }
31
+ }
32
+
28
33
  def initialize(schema)
29
34
  @schema = schema
35
+ @handlers = {
36
+ class: nil,
37
+ handler: nil,
38
+ subclass_handlers: Hash.new(&NEW_HANDLER_HASH),
39
+ }
40
+ end
41
+
42
+ # @api private
43
+ def each_rescue
44
+ handlers = @handlers.values
45
+ while (handler = handlers.shift) do
46
+ yield(handler[:class], handler[:handler])
47
+ handlers.concat(handler[:subclass_handlers].values)
48
+ end
30
49
  end
31
50
 
32
- class NullErrorHandler
33
- def self.with_error_handling(_ctx)
34
- yield
51
+ # Register this handler, updating the
52
+ # internal handler index to maintain least-to-most specific.
53
+ #
54
+ # @param error_class [Class<Exception>]
55
+ # @param error_handler [Proc]
56
+ # @return [void]
57
+ def rescue_from(error_class, error_handler)
58
+ subclasses_handlers = {}
59
+ this_level_subclasses = []
60
+ # During this traversal, do two things:
61
+ # - Identify any already-registered subclasses of this error class
62
+ # and gather them up to be inserted _under_ this class
63
+ # - Find the point in the index where this handler should be inserted
64
+ # (That is, _under_ any superclasses, or at top-level, if there are no superclasses registered)
65
+ handlers = @handlers[:subclass_handlers]
66
+ while (handlers) do
67
+ this_level_subclasses.clear
68
+ # First, identify already-loaded handlers that belong
69
+ # _under_ this one. (That is, they're handlers
70
+ # for subclasses of `error_class`.)
71
+ handlers.each do |err_class, handler|
72
+ if err_class < error_class
73
+ subclasses_handlers[err_class] = handler
74
+ this_level_subclasses << err_class
75
+ end
76
+ end
77
+ # Any handlers that we'll be moving, delete them from this point in the index
78
+ this_level_subclasses.each do |err_class|
79
+ handlers.delete(err_class)
80
+ end
81
+
82
+ # See if any keys in this hash are superclasses of this new class:
83
+ next_index_point = handlers.find { |err_class, handler| error_class < err_class }
84
+ if next_index_point
85
+ handlers = next_index_point[1][:subclass_handlers]
86
+ else
87
+ # this new handler doesn't belong to any sub-handlers,
88
+ # so insert it in the current set of `handlers`
89
+ break
90
+ end
35
91
  end
92
+ # Having found the point at which to insert this handler,
93
+ # register it and merge any subclass handlers back in at this point.
94
+ this_class_handlers = handlers[error_class]
95
+ this_class_handlers[:handler] = error_handler
96
+ this_class_handlers[:subclass_handlers].merge!(subclasses_handlers)
97
+ nil
36
98
  end
37
99
 
38
100
  # Call the given block with the schema's configured error handlers.
@@ -44,8 +106,7 @@ module GraphQL
44
106
  def with_error_handling(ctx)
45
107
  yield
46
108
  rescue StandardError => err
47
- rescues = ctx.schema.rescues
48
- _err_class, handler = rescues.find { |err_class, handler| err.is_a?(err_class) }
109
+ handler = find_handler_for(err.class)
49
110
  if handler
50
111
  runtime_info = ctx.namespace(:interpreter) || {}
51
112
  obj = runtime_info[:current_object]
@@ -54,11 +115,48 @@ module GraphQL
54
115
  if obj.is_a?(GraphQL::Schema::Object)
55
116
  obj = obj.object
56
117
  end
57
- handler.call(err, obj, args, ctx, field)
118
+ handler[:handler].call(err, obj, args, ctx, field)
58
119
  else
59
120
  raise err
60
121
  end
61
122
  end
123
+
124
+ # @return [Proc, nil] The handler for `error_class`, if one was registered on this schema or inherited
125
+ def find_handler_for(error_class)
126
+ handlers = @handlers[:subclass_handlers]
127
+ handler = nil
128
+ while (handlers) do
129
+ _err_class, next_handler = handlers.find { |err_class, handler| error_class <= err_class }
130
+ if next_handler
131
+ handlers = next_handler[:subclass_handlers]
132
+ handler = next_handler
133
+ else
134
+ # Don't reassign `handler` --
135
+ # let the previous assignment carry over outside this block.
136
+ break
137
+ end
138
+ end
139
+
140
+ # check for a handler from a parent class:
141
+ if @schema.superclass.respond_to?(:error_handler) && (parent_errors = @schema.superclass.error_handler)
142
+ parent_handler = parent_errors.find_handler_for(error_class)
143
+ end
144
+
145
+ # If the inherited handler is more specific than the one defined here,
146
+ # use it.
147
+ # If it's a tie (or there is no parent handler), use the one defined here.
148
+ # If there's an inherited one, but not one defined here, use the inherited one.
149
+ # Otherwise, there's no handler for this error, return `nil`.
150
+ if parent_handler && handler && parent_handler[:class] < handler[:class]
151
+ parent_handler
152
+ elsif handler
153
+ handler
154
+ elsif parent_handler
155
+ parent_handler
156
+ else
157
+ nil
158
+ end
159
+ end
62
160
  end
63
161
  end
64
162
  end
@@ -6,7 +6,7 @@ module GraphQL
6
6
  class Execute
7
7
 
8
8
  # @api private
9
- class Skip; end
9
+ class Skip < GraphQL::Error; end
10
10
 
11
11
  # Just a singleton for implementing {Query::Context#skip}
12
12
  # @api private
@@ -4,7 +4,6 @@ require "graphql/execution/interpreter/argument_value"
4
4
  require "graphql/execution/interpreter/arguments"
5
5
  require "graphql/execution/interpreter/arguments_cache"
6
6
  require "graphql/execution/interpreter/execution_errors"
7
- require "graphql/execution/interpreter/hash_response"
8
7
  require "graphql/execution/interpreter/runtime"
9
8
  require "graphql/execution/interpreter/resolve"
10
9
  require "graphql/execution/interpreter/handles_raw_value"
@@ -19,7 +18,7 @@ module GraphQL
19
18
  def execute(_operation, _root_type, query)
20
19
  runtime = evaluate(query)
21
20
  sync_lazies(query: query)
22
- runtime.final_value
21
+ runtime.response
23
22
  end
24
23
 
25
24
  def self.use(schema_class)
@@ -57,7 +56,7 @@ module GraphQL
57
56
 
58
57
  def self.finish_query(query, _multiplex)
59
58
  {
60
- "data" => query.context.namespace(:interpreter)[:runtime].final_value
59
+ "data" => query.context.namespace(:interpreter)[:runtime].response
61
60
  }
62
61
  end
63
62
 
@@ -67,10 +66,7 @@ module GraphQL
67
66
  # Although queries in a multiplex _share_ an Interpreter instance,
68
67
  # they also have another item of state, which is private to that query
69
68
  # in particular, assign it here:
70
- runtime = Runtime.new(
71
- query: query,
72
- response: HashResponse.new,
73
- )
69
+ runtime = Runtime.new(query: query)
74
70
  query.context.namespace(:interpreter)[:runtime] = runtime
75
71
 
76
72
  query.trace("execute_query", {query: query}) do
@@ -91,7 +87,7 @@ module GraphQL
91
87
  final_values = queries.map do |query|
92
88
  runtime = query.context.namespace(:interpreter)[:runtime]
93
89
  # it might not be present if the query has an error
94
- runtime ? runtime.final_value : nil
90
+ runtime ? runtime.response : nil
95
91
  end
96
92
  final_values.compact!
97
93
  tracer.trace("execute_query_lazy", {multiplex: multiplex, query: query}) do
@@ -8,6 +8,28 @@ module GraphQL
8
8
  #
9
9
  # @api private
10
10
  class Runtime
11
+
12
+ module GraphQLResult
13
+ # These methods are private concerns of GraphQL-Ruby,
14
+ # they aren't guaranteed to continue working in the future.
15
+ attr_accessor :graphql_dead, :graphql_parent, :graphql_result_name
16
+ # Although these are used by only one of the Result classes,
17
+ # it's handy to have the methods implemented on both (even though they just return `nil`)
18
+ # because it makes it easy to check if anything is assigned.
19
+ # @return [nil, Array<String>]
20
+ attr_accessor :graphql_non_null_field_names
21
+ # @return [nil, true]
22
+ attr_accessor :graphql_non_null_list_items
23
+ end
24
+
25
+ class GraphQLResultHash < Hash
26
+ include GraphQLResult
27
+ end
28
+
29
+ class GraphQLResultArray < Array
30
+ include GraphQLResult
31
+ end
32
+
11
33
  # @return [GraphQL::Query]
12
34
  attr_reader :query
13
35
 
@@ -17,24 +39,23 @@ module GraphQL
17
39
  # @return [GraphQL::Query::Context]
18
40
  attr_reader :context
19
41
 
20
- def initialize(query:, response:)
42
+ # @return [Hash]
43
+ attr_reader :response
44
+
45
+ def initialize(query:)
21
46
  @query = query
22
47
  @dataloader = query.multiplex.dataloader
23
48
  @schema = query.schema
24
49
  @context = query.context
25
50
  @multiplex_context = query.multiplex.context
26
51
  @interpreter_context = @context.namespace(:interpreter)
27
- @response = response
28
- @dead_paths = {}
29
- @types_at_paths = {}
52
+ @response = GraphQLResultHash.new
30
53
  # A cache of { Class => { String => Schema::Field } }
31
54
  # Which assumes that MyObject.get_field("myField") will return the same field
32
55
  # during the lifetime of a query
33
56
  @fields_cache = Hash.new { |h, k| h[k] = {} }
34
- end
35
-
36
- def final_value
37
- @response.final_value
57
+ # { Class => Boolean }
58
+ @lazy_cache = {}
38
59
  end
39
60
 
40
61
  def inspect
@@ -50,24 +71,28 @@ module GraphQL
50
71
  root_type = schema.root_type_for_operation(root_op_type)
51
72
  path = []
52
73
  set_all_interpreter_context(query.root_value, nil, nil, path)
53
- object_proxy = authorized_new(root_type, query.root_value, context, path)
74
+ object_proxy = authorized_new(root_type, query.root_value, context)
54
75
  object_proxy = schema.sync_lazy(object_proxy)
76
+
55
77
  if object_proxy.nil?
56
78
  # Root .authorized? returned false.
57
- write_in_response(path, nil)
79
+ @response = nil
58
80
  else
59
- gathered_selections = gather_selections(object_proxy, root_type, root_operation.selections)
60
- # Make the first fiber which will begin execution
61
- @dataloader.append_job {
62
- evaluate_selections(
63
- path,
64
- context.scoped_context,
65
- object_proxy,
66
- root_type,
67
- root_op_type == "mutation",
68
- gathered_selections,
69
- )
70
- }
81
+ resolve_with_directives(object_proxy, root_operation) do # execute query level directives
82
+ gathered_selections = gather_selections(object_proxy, root_type, root_operation.selections)
83
+ # Make the first fiber which will begin execution
84
+ @dataloader.append_job {
85
+ evaluate_selections(
86
+ path,
87
+ context.scoped_context,
88
+ object_proxy,
89
+ root_type,
90
+ root_op_type == "mutation",
91
+ gathered_selections,
92
+ @response,
93
+ )
94
+ }
95
+ end
71
96
  end
72
97
  delete_interpreter_context(:current_path)
73
98
  delete_interpreter_context(:current_field)
@@ -134,13 +159,13 @@ module GraphQL
134
159
  NO_ARGS = {}.freeze
135
160
 
136
161
  # @return [void]
137
- def evaluate_selections(path, scoped_context, owner_object, owner_type, is_eager_selection, gathered_selections)
162
+ def evaluate_selections(path, scoped_context, owner_object, owner_type, is_eager_selection, gathered_selections, selections_result)
138
163
  set_all_interpreter_context(owner_object, nil, nil, path)
139
164
 
140
165
  gathered_selections.each do |result_name, field_ast_nodes_or_ast_node|
141
166
  @dataloader.append_job {
142
167
  evaluate_selection(
143
- path, result_name, field_ast_nodes_or_ast_node, scoped_context, owner_object, owner_type, is_eager_selection
168
+ path, result_name, field_ast_nodes_or_ast_node, scoped_context, owner_object, owner_type, is_eager_selection, selections_result
144
169
  )
145
170
  }
146
171
  end
@@ -151,7 +176,7 @@ module GraphQL
151
176
  attr_reader :progress_path
152
177
 
153
178
  # @return [void]
154
- def evaluate_selection(path, result_name, field_ast_nodes_or_ast_node, scoped_context, owner_object, owner_type, is_eager_field)
179
+ def evaluate_selection(path, result_name, field_ast_nodes_or_ast_node, scoped_context, owner_object, owner_type, is_eager_field, selections_result) # rubocop:disable Metrics/ParameterLists
155
180
  # As a performance optimization, the hash key will be a `Node` if
156
181
  # there's only one selection of the field. But if there are multiple
157
182
  # selections of the field, it will be an Array of nodes
@@ -185,7 +210,9 @@ module GraphQL
185
210
  # This seems janky, but we need to know
186
211
  # the field's return type at this path in order
187
212
  # to propagate `null`
188
- set_type_at_path(next_path, return_type)
213
+ if return_type.non_null?
214
+ (selections_result.graphql_non_null_field_names ||= []).push(result_name)
215
+ end
189
216
  # Set this before calling `run_with_directives`, so that the directive can have the latest path
190
217
  set_all_interpreter_context(nil, field_defn, nil, next_path)
191
218
 
@@ -193,27 +220,27 @@ module GraphQL
193
220
  object = owner_object
194
221
 
195
222
  if is_introspection
196
- object = authorized_new(field_defn.owner, object, context, next_path)
223
+ object = authorized_new(field_defn.owner, object, context)
197
224
  end
198
225
 
199
226
  total_args_count = field_defn.arguments.size
200
227
  if total_args_count == 0
201
228
  kwarg_arguments = GraphQL::Execution::Interpreter::Arguments::EMPTY
202
- evaluate_selection_with_args(kwarg_arguments, field_defn, next_path, ast_node, field_ast_nodes, scoped_context, owner_type, object, is_eager_field)
229
+ evaluate_selection_with_args(kwarg_arguments, field_defn, next_path, ast_node, field_ast_nodes, scoped_context, owner_type, object, is_eager_field, result_name, selections_result)
203
230
  else
204
231
  # TODO remove all arguments(...) usages?
205
232
  @query.arguments_cache.dataload_for(ast_node, field_defn, object) do |resolved_arguments|
206
- evaluate_selection_with_args(resolved_arguments, field_defn, next_path, ast_node, field_ast_nodes, scoped_context, owner_type, object, is_eager_field)
233
+ evaluate_selection_with_args(resolved_arguments, field_defn, next_path, ast_node, field_ast_nodes, scoped_context, owner_type, object, is_eager_field, result_name, selections_result)
207
234
  end
208
235
  end
209
236
  end
210
237
 
211
- def evaluate_selection_with_args(kwarg_arguments, field_defn, next_path, ast_node, field_ast_nodes, scoped_context, owner_type, object, is_eager_field) # rubocop:disable Metrics/ParameterLists
238
+ def evaluate_selection_with_args(kwarg_arguments, field_defn, next_path, ast_node, field_ast_nodes, scoped_context, owner_type, object, is_eager_field, result_name, selection_result) # rubocop:disable Metrics/ParameterLists
212
239
  context.scoped_context = scoped_context
213
240
  return_type = field_defn.type
214
- after_lazy(kwarg_arguments, owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, scoped_context: context.scoped_context, owner_object: object, arguments: kwarg_arguments) do |resolved_arguments|
241
+ after_lazy(kwarg_arguments, owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, scoped_context: context.scoped_context, owner_object: object, arguments: kwarg_arguments, result_name: result_name, result: selection_result) do |resolved_arguments|
215
242
  if resolved_arguments.is_a?(GraphQL::ExecutionError) || resolved_arguments.is_a?(GraphQL::UnauthorizedError)
216
- continue_value(next_path, resolved_arguments, owner_type, field_defn, return_type.non_null?, ast_node)
243
+ continue_value(next_path, resolved_arguments, owner_type, field_defn, return_type.non_null?, ast_node, result_name, selection_result)
217
244
  next
218
245
  end
219
246
 
@@ -246,11 +273,17 @@ module GraphQL
246
273
  # Use this flag to tell Interpreter::Arguments to add itself
247
274
  # to the keyword args hash _before_ freezing everything.
248
275
  extra_args[:argument_details] = :__arguments_add_self
276
+ when :irep_node
277
+ # This is used by `__typename` in order to support the legacy runtime,
278
+ # but it has no use here (and it's always `nil`).
279
+ # Stop adding it here to avoid the overhead of `.merge_extras` below.
249
280
  else
250
281
  extra_args[extra] = field_defn.fetch_extra(extra, context)
251
282
  end
252
283
  end
253
- resolved_arguments = resolved_arguments.merge_extras(extra_args)
284
+ if extra_args.any?
285
+ resolved_arguments = resolved_arguments.merge_extras(extra_args)
286
+ end
254
287
  resolved_arguments.keyword_arguments
255
288
  end
256
289
 
@@ -275,13 +308,10 @@ module GraphQL
275
308
  rescue GraphQL::ExecutionError => err
276
309
  err
277
310
  end
278
- after_lazy(app_result, owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, scoped_context: context.scoped_context, owner_object: object, arguments: kwarg_arguments) do |inner_result|
279
- continue_value = continue_value(next_path, inner_result, owner_type, field_defn, return_type.non_null?, ast_node)
280
- if RawValue === continue_value
281
- # Write raw value directly to the response without resolving nested objects
282
- write_in_response(next_path, continue_value.resolve)
283
- elsif HALT != continue_value
284
- continue_field(next_path, continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, kwarg_arguments)
311
+ after_lazy(app_result, owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, scoped_context: context.scoped_context, owner_object: object, arguments: kwarg_arguments, result_name: result_name, result: selection_result) do |inner_result|
312
+ continue_value = continue_value(next_path, inner_result, owner_type, field_defn, return_type.non_null?, ast_node, result_name, selection_result)
313
+ if HALT != continue_value
314
+ continue_field(next_path, continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, kwarg_arguments, result_name, selection_result)
285
315
  end
286
316
  end
287
317
  end
@@ -298,39 +328,109 @@ module GraphQL
298
328
  end
299
329
  end
300
330
 
331
+ def dead_result?(selection_result)
332
+ r = selection_result
333
+ while r
334
+ if r.graphql_dead
335
+ return true
336
+ else
337
+ r = r.graphql_parent
338
+ end
339
+ end
340
+ false
341
+ end
342
+
343
+ def set_result(selection_result, result_name, value)
344
+ if !dead_result?(selection_result)
345
+ if value.nil? &&
346
+ ( # there are two conditions under which `nil` is not allowed in the response:
347
+ (selection_result.graphql_non_null_list_items) || # this value would be written into a list that doesn't allow nils
348
+ ((nn = selection_result.graphql_non_null_field_names) && nn.include?(result_name)) # this value would be written into a field that doesn't allow nils
349
+ )
350
+ # This is an invalid nil that should be propagated
351
+ # One caller of this method passes a block,
352
+ # namely when application code returns a `nil` to GraphQL and it doesn't belong there.
353
+ # The other possibility for reaching here is when a field returns an ExecutionError, so we write
354
+ # `nil` to the response, not knowing whether it's an invalid `nil` or not.
355
+ # (And in that case, we don't have to call the schema's handler, since it's not a bug in the application.)
356
+ # TODO the code is trying to tell me something.
357
+ yield if block_given?
358
+ parent = selection_result.graphql_parent
359
+ name_in_parent = selection_result.graphql_result_name
360
+ if parent.nil? # This is a top-level result hash
361
+ @response = nil
362
+ else
363
+ set_result(parent, name_in_parent, nil)
364
+ # This is odd, but it's how it used to work. Even if `parent` _would_ accept
365
+ # a `nil`, it's marked dead. TODO: check the spec, is there a reason for this?
366
+ parent.graphql_dead = true
367
+ end
368
+ else
369
+ selection_result[result_name] = value
370
+ end
371
+ end
372
+ end
373
+
301
374
  HALT = Object.new
302
- def continue_value(path, value, parent_type, field, is_non_null, ast_node)
303
- if value.nil?
375
+ def continue_value(path, value, parent_type, field, is_non_null, ast_node, result_name, selection_result) # rubocop:disable Metrics/ParameterLists
376
+ case value
377
+ when nil
304
378
  if is_non_null
305
- err = parent_type::InvalidNullError.new(parent_type, field, value)
306
- write_invalid_null_in_response(path, err)
379
+ set_result(selection_result, result_name, nil) do
380
+ # This block is called if `result_name` is not dead. (Maybe a previous invalid nil caused it be marked dead.)
381
+ err = parent_type::InvalidNullError.new(parent_type, field, value)
382
+ schema.type_error(err, context)
383
+ end
307
384
  else
308
- write_in_response(path, nil)
385
+ set_result(selection_result, result_name, nil)
309
386
  end
310
387
  HALT
311
- elsif value.is_a?(GraphQL::ExecutionError)
312
- value.path ||= path
313
- value.ast_node ||= ast_node
314
- write_execution_errors_in_response(path, [value])
315
- HALT
316
- elsif value.is_a?(Array) && value.any? && value.all? { |v| v.is_a?(GraphQL::ExecutionError) }
317
- value.each_with_index do |error, index|
318
- error.ast_node ||= ast_node
319
- error.path ||= path + (field.type.list? ? [index] : [])
388
+ when GraphQL::Error
389
+ # Handle these cases inside a single `when`
390
+ # to avoid the overhead of checking three different classes
391
+ # every time.
392
+ if value.is_a?(GraphQL::ExecutionError)
393
+ if !dead_result?(selection_result)
394
+ value.path ||= path
395
+ value.ast_node ||= ast_node
396
+ context.errors << value
397
+ set_result(selection_result, result_name, nil)
398
+ end
399
+ HALT
400
+ elsif value.is_a?(GraphQL::UnauthorizedError)
401
+ # this hook might raise & crash, or it might return
402
+ # a replacement value
403
+ next_value = begin
404
+ schema.unauthorized_object(value)
405
+ rescue GraphQL::ExecutionError => err
406
+ err
407
+ end
408
+ continue_value(path, next_value, parent_type, field, is_non_null, ast_node, result_name, selection_result)
409
+ elsif GraphQL::Execution::Execute::SKIP == value
410
+ HALT
411
+ else
412
+ # What could this actually _be_? Anyhow,
413
+ # preserve the default behavior of doing nothing with it.
414
+ value
320
415
  end
321
- write_execution_errors_in_response(path, value)
322
- HALT
323
- elsif value.is_a?(GraphQL::UnauthorizedError)
324
- # this hook might raise & crash, or it might return
325
- # a replacement value
326
- next_value = begin
327
- schema.unauthorized_object(value)
328
- rescue GraphQL::ExecutionError => err
329
- err
416
+ when Array
417
+ # It's an array full of execution errors; add them all.
418
+ if value.any? && value.all? { |v| v.is_a?(GraphQL::ExecutionError) }
419
+ if !dead_result?(selection_result)
420
+ value.each_with_index do |error, index|
421
+ error.ast_node ||= ast_node
422
+ error.path ||= path + (field.type.list? ? [index] : [])
423
+ context.errors << error
424
+ end
425
+ set_result(selection_result, result_name, nil)
426
+ end
427
+ HALT
428
+ else
429
+ value
330
430
  end
331
-
332
- continue_value(path, next_value, parent_type, field, is_non_null, ast_node)
333
- elsif GraphQL::Execution::Execute::SKIP == value
431
+ when GraphQL::Execution::Interpreter::RawValue
432
+ # Write raw value directly to the response without resolving nested objects
433
+ set_result(selection_result, result_name, value.resolve)
334
434
  HALT
335
435
  else
336
436
  value
@@ -345,17 +445,22 @@ module GraphQL
345
445
  # Location information from `path` and `ast_node`.
346
446
  #
347
447
  # @return [Lazy, Array, Hash, Object] Lazy, Array, and Hash are all traversed to resolve lazy values later
348
- def continue_field(path, value, owner_type, field, current_type, ast_node, next_selections, is_non_null, owner_object, arguments) # rubocop:disable Metrics/ParameterLists
448
+ def continue_field(path, value, owner_type, field, current_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result) # rubocop:disable Metrics/ParameterLists
449
+ if current_type.non_null?
450
+ current_type = current_type.of_type
451
+ is_non_null = true
452
+ end
453
+
349
454
  case current_type.kind.name
350
455
  when "SCALAR", "ENUM"
351
456
  r = current_type.coerce_result(value, context)
352
- write_in_response(path, r)
457
+ set_result(selection_result, result_name, r)
353
458
  r
354
459
  when "UNION", "INTERFACE"
355
460
  resolved_type_or_lazy, resolved_value = resolve_type(current_type, value, path)
356
461
  resolved_value ||= value
357
462
 
358
- after_lazy(resolved_type_or_lazy, owner: current_type, path: path, ast_node: ast_node, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments, trace: false) do |resolved_type|
463
+ after_lazy(resolved_type_or_lazy, owner: current_type, path: path, ast_node: ast_node, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result) do |resolved_type|
359
464
  possible_types = query.possible_types(current_type)
360
465
 
361
466
  if !possible_types.include?(resolved_type)
@@ -363,46 +468,52 @@ module GraphQL
363
468
  err_class = current_type::UnresolvedTypeError
364
469
  type_error = err_class.new(resolved_value, field, parent_type, resolved_type, possible_types)
365
470
  schema.type_error(type_error, context)
366
- write_in_response(path, nil)
471
+ set_result(selection_result, result_name, nil)
367
472
  nil
368
473
  else
369
- continue_field(path, resolved_value, owner_type, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments)
474
+ continue_field(path, resolved_value, owner_type, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result)
370
475
  end
371
476
  end
372
477
  when "OBJECT"
373
478
  object_proxy = begin
374
- authorized_new(current_type, value, context, path)
479
+ authorized_new(current_type, value, context)
375
480
  rescue GraphQL::ExecutionError => err
376
481
  err
377
482
  end
378
- after_lazy(object_proxy, owner: current_type, path: path, ast_node: ast_node, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments, trace: false) do |inner_object|
379
- continue_value = continue_value(path, inner_object, owner_type, field, is_non_null, ast_node)
483
+ after_lazy(object_proxy, owner: current_type, path: path, ast_node: ast_node, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result) do |inner_object|
484
+ continue_value = continue_value(path, inner_object, owner_type, field, is_non_null, ast_node, result_name, selection_result)
380
485
  if HALT != continue_value
381
- response_hash = {}
382
- write_in_response(path, response_hash)
486
+ response_hash = GraphQLResultHash.new
487
+ response_hash.graphql_parent = selection_result
488
+ response_hash.graphql_result_name = result_name
489
+ set_result(selection_result, result_name, response_hash)
383
490
  gathered_selections = gather_selections(continue_value, current_type, next_selections)
384
- evaluate_selections(path, context.scoped_context, continue_value, current_type, false, gathered_selections)
491
+ evaluate_selections(path, context.scoped_context, continue_value, current_type, false, gathered_selections, response_hash)
385
492
  response_hash
386
493
  end
387
494
  end
388
495
  when "LIST"
389
- response_list = []
390
- write_in_response(path, response_list)
391
496
  inner_type = current_type.of_type
497
+ response_list = GraphQLResultArray.new
498
+ response_list.graphql_non_null_list_items = inner_type.non_null?
499
+ response_list.graphql_parent = selection_result
500
+ response_list.graphql_result_name = result_name
501
+ set_result(selection_result, result_name, response_list)
502
+
392
503
  idx = 0
393
504
  scoped_context = context.scoped_context
394
505
  begin
395
506
  value.each do |inner_value|
396
507
  next_path = path.dup
397
508
  next_path << idx
509
+ this_idx = idx
398
510
  next_path.freeze
399
511
  idx += 1
400
- set_type_at_path(next_path, inner_type)
401
512
  # This will update `response_list` with the lazy
402
- after_lazy(inner_value, owner: inner_type, path: next_path, ast_node: ast_node, scoped_context: scoped_context, field: field, owner_object: owner_object, arguments: arguments) do |inner_inner_value|
403
- continue_value = continue_value(next_path, inner_inner_value, owner_type, field, inner_type.non_null?, ast_node)
513
+ after_lazy(inner_value, owner: inner_type, path: next_path, ast_node: ast_node, scoped_context: scoped_context, field: field, owner_object: owner_object, arguments: arguments, result_name: this_idx, result: response_list) do |inner_inner_value|
514
+ continue_value = continue_value(next_path, inner_inner_value, owner_type, field, inner_type.non_null?, ast_node, this_idx, response_list)
404
515
  if HALT != continue_value
405
- continue_field(next_path, continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments)
516
+ continue_field(next_path, continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments, this_idx, response_list)
406
517
  end
407
518
  end
408
519
  end
@@ -418,11 +529,6 @@ module GraphQL
418
529
  end
419
530
 
420
531
  response_list
421
- when "NON_NULL"
422
- inner_type = current_type.of_type
423
- # Don't `set_type_at_path` because we want the static type,
424
- # we're going to use that to determine whether a `nil` should be propagated or not.
425
- continue_field(path, value, owner_type, field, inner_type, ast_node, next_selections, true, owner_object, arguments)
426
532
  else
427
533
  raise "Invariant: Unhandled type kind #{current_type.kind} (#{current_type})"
428
534
  end
@@ -482,9 +588,8 @@ module GraphQL
482
588
  # @param eager [Boolean] Set to `true` for mutation root fields only
483
589
  # @param trace [Boolean] If `false`, don't wrap this with field tracing
484
590
  # @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.
485
- def after_lazy(lazy_obj, owner:, field:, path:, scoped_context:, owner_object:, arguments:, ast_node:, eager: false, trace: true, &block)
486
- set_all_interpreter_context(owner_object, field, arguments, path)
487
- if schema.lazy?(lazy_obj)
591
+ def after_lazy(lazy_obj, owner:, field:, path:, scoped_context:, owner_object:, arguments:, ast_node:, result:, result_name:, eager: false, trace: true, &block)
592
+ if lazy?(lazy_obj)
488
593
  lazy = GraphQL::Execution::Lazy.new(path: path, field: field) do
489
594
  set_all_interpreter_context(owner_object, field, arguments, path)
490
595
  context.scoped_context = scoped_context
@@ -503,16 +608,17 @@ module GraphQL
503
608
  rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => err
504
609
  err
505
610
  end
506
- after_lazy(inner_obj, owner: owner, field: field, path: path, ast_node: ast_node, scoped_context: context.scoped_context, owner_object: owner_object, arguments: arguments, eager: eager, trace: trace, &block)
611
+ yield(inner_obj)
507
612
  end
508
613
 
509
614
  if eager
510
615
  lazy.value
511
616
  else
512
- write_in_response(path, lazy)
617
+ set_result(result, result_name, lazy)
513
618
  lazy
514
619
  end
515
620
  else
621
+ set_all_interpreter_context(owner_object, field, arguments, path)
516
622
  yield(lazy_obj)
517
623
  end
518
624
  end
@@ -526,85 +632,6 @@ module GraphQL
526
632
  end
527
633
  end
528
634
 
529
- def write_invalid_null_in_response(path, invalid_null_error)
530
- if !dead_path?(path)
531
- schema.type_error(invalid_null_error, context)
532
- write_in_response(path, nil)
533
- add_dead_path(path)
534
- end
535
- end
536
-
537
- def write_execution_errors_in_response(path, errors)
538
- if !dead_path?(path)
539
- errors.each do |v|
540
- context.errors << v
541
- end
542
- write_in_response(path, nil)
543
- add_dead_path(path)
544
- end
545
- end
546
-
547
- def write_in_response(path, value)
548
- if dead_path?(path)
549
- return
550
- else
551
- if value.nil? && path.any? && type_at(path).non_null?
552
- # This nil is invalid, try writing it at the previous spot
553
- propagate_path = path[0..-2]
554
- write_in_response(propagate_path, value)
555
- add_dead_path(propagate_path)
556
- else
557
- @response.write(path, value)
558
- end
559
- end
560
- end
561
-
562
- def value_at(path)
563
- i = 0
564
- value = @response.final_value
565
- while value && (part = path[i])
566
- value = value[part]
567
- i += 1
568
- end
569
- value
570
- end
571
-
572
- # To propagate nulls, we have to know what the field type was
573
- # at previous parts of the response.
574
- # This hash matches the response
575
- def type_at(path)
576
- @types_at_paths.fetch(path)
577
- end
578
-
579
- def set_type_at_path(path, type)
580
- @types_at_paths[path] = type
581
- nil
582
- end
583
-
584
- # Mark `path` as having been permanently nulled out.
585
- # No values will be added beyond that path.
586
- def add_dead_path(path)
587
- dead = @dead_paths
588
- path.each do |part|
589
- dead = dead[part] ||= {}
590
- end
591
- dead[:__dead] = true
592
- end
593
-
594
- def dead_path?(path)
595
- res = @dead_paths
596
- path.each do |part|
597
- if res
598
- if res[:__dead]
599
- break
600
- else
601
- res = res[part]
602
- end
603
- end
604
- end
605
- res && res[:__dead]
606
- end
607
-
608
635
  # Set this pair in the Query context, but also in the interpeter namespace,
609
636
  # for compatibility.
610
637
  def set_interpreter_context(key, value)
@@ -623,7 +650,7 @@ module GraphQL
623
650
  query.resolve_type(type, value)
624
651
  end
625
652
 
626
- if schema.lazy?(resolved_type)
653
+ if lazy?(resolved_type)
627
654
  GraphQL::Execution::Lazy.new do
628
655
  query.trace("resolve_type_lazy", trace_payload) do
629
656
  schema.sync_lazy(resolved_type)
@@ -634,22 +661,14 @@ module GraphQL
634
661
  end
635
662
  end
636
663
 
637
- def authorized_new(type, value, context, path)
638
- trace_payload = { context: context, type: type, object: value, path: path }
639
-
640
- auth_val = context.query.trace("authorized", trace_payload) do
641
- type.authorized_new(value, context)
642
- end
664
+ def authorized_new(type, value, context)
665
+ type.authorized_new(value, context)
666
+ end
643
667
 
644
- if context.schema.lazy?(auth_val)
645
- GraphQL::Execution::Lazy.new do
646
- context.query.trace("authorized_lazy", trace_payload) do
647
- context.schema.sync_lazy(auth_val)
648
- end
649
- end
650
- else
651
- auth_val
652
- end
668
+ def lazy?(object)
669
+ @lazy_cache.fetch(object.class) {
670
+ @lazy_cache[object.class] = @schema.lazy?(object)
671
+ }
653
672
  end
654
673
  end
655
674
  end