graphql 1.12.6 → 1.12.11

Sign up to get free protection for your applications and to get access to all the features.
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