graphql 1.12.10 → 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.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9d314c3a4dbbf6bded442bea6c2fcdd68b0cd7730ebb5e807d3677ffdc838268
4
- data.tar.gz: 60a24771181bb8a479f768a4b17f3a474ff9bbc339027b94e18bf92554df296c
3
+ metadata.gz: 9e46a535c108ff0ae582fcfa598b21cda8faad15142629b1cdae772f3d24cd7e
4
+ data.tar.gz: 3185ec249d624c6b23920d9d9fb47167ac031b7645053ff705e3c04acc0fa571
5
5
  SHA512:
6
- metadata.gz: 25f941249c01ce4dcfe3522dfc7b045b6f2bd3888e1f21b2e25a3ae5de74f0de5f9721c998d6ddd33eb681339c3e883edcdc9d76a196c148b688a1264b1761fa
7
- data.tar.gz: 91bde8d0102ae6874511656b6e11140b511aa1813d19b70b91a168cf58cbfc95eb83a6244137fc06abeae19f15694ca005c94c6e728085d0b8be3ad3bb964ebc
6
+ metadata.gz: 989c1074a6de63a31002d2bd92da719e1b08682d3eefe361add671a75c1991173b13cc6516f8ede8ff5bcf7499935e514cd8d7d57a2940e85160bc427538e99c
7
+ data.tar.gz: cb40ba585e721d28856951312755ef84bdd698a7299b1a383dd4f9db77d8b137e8d21ee9a8784b14780b4230a5a5325520d0ceadf90f6bd055781cc7159257b5
@@ -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
@@ -136,26 +136,24 @@ module GraphQL
136
136
  # This is where an evented approach would be even better -- can we tell which
137
137
  # fibers are ready to continue, and continue execution there?
138
138
  #
139
- source_fiber_stack = if (first_source_fiber = create_source_fiber)
139
+ source_fiber_queue = if (first_source_fiber = create_source_fiber)
140
140
  [first_source_fiber]
141
141
  else
142
142
  nil
143
143
  end
144
144
 
145
- if source_fiber_stack
146
- # Use a stack with `.pop` here so that when a source causes another source to become pending,
147
- # that newly-pending source will run _before_ the one that depends on it.
148
- # (See below where the old fiber is pushed to the stack, then the new fiber is pushed on the stack.)
149
- while (outer_source_fiber = source_fiber_stack.pop)
145
+ if source_fiber_queue
146
+ while (outer_source_fiber = source_fiber_queue.shift)
150
147
  resume(outer_source_fiber)
151
148
 
152
- if outer_source_fiber.alive?
153
- source_fiber_stack << outer_source_fiber
154
- end
155
149
  # If this source caused more sources to become pending, run those before running this one again:
156
150
  next_source_fiber = create_source_fiber
157
151
  if next_source_fiber
158
- 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
159
157
  end
160
158
  end
161
159
  end
@@ -224,16 +222,16 @@ module GraphQL
224
222
  #
225
223
  # @see https://github.com/rmosolgo/graphql-ruby/issues/3449
226
224
  def spawn_fiber
227
- fiber_locals = {}
225
+ fiber_locals = {}
228
226
 
229
227
  Thread.current.keys.each do |fiber_var_key|
230
228
  fiber_locals[fiber_var_key] = Thread.current[fiber_var_key]
231
- end
229
+ end
232
230
 
233
- Fiber.new do
231
+ Fiber.new do
234
232
  fiber_locals.each { |k, v| Thread.current[k] = v }
235
233
  yield
236
- end
234
+ end
237
235
  end
238
236
  end
239
237
  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
@@ -55,7 +76,7 @@ module GraphQL
55
76
 
56
77
  if object_proxy.nil?
57
78
  # Root .authorized? returned false.
58
- write_in_response(path, nil)
79
+ @response = nil
59
80
  else
60
81
  resolve_with_directives(object_proxy, root_operation) do # execute query level directives
61
82
  gathered_selections = gather_selections(object_proxy, root_type, root_operation.selections)
@@ -68,6 +89,7 @@ module GraphQL
68
89
  root_type,
69
90
  root_op_type == "mutation",
70
91
  gathered_selections,
92
+ @response,
71
93
  )
72
94
  }
73
95
  end
@@ -137,13 +159,13 @@ module GraphQL
137
159
  NO_ARGS = {}.freeze
138
160
 
139
161
  # @return [void]
140
- 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)
141
163
  set_all_interpreter_context(owner_object, nil, nil, path)
142
164
 
143
165
  gathered_selections.each do |result_name, field_ast_nodes_or_ast_node|
144
166
  @dataloader.append_job {
145
167
  evaluate_selection(
146
- 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
147
169
  )
148
170
  }
149
171
  end
@@ -154,7 +176,7 @@ module GraphQL
154
176
  attr_reader :progress_path
155
177
 
156
178
  # @return [void]
157
- 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
158
180
  # As a performance optimization, the hash key will be a `Node` if
159
181
  # there's only one selection of the field. But if there are multiple
160
182
  # selections of the field, it will be an Array of nodes
@@ -188,7 +210,9 @@ module GraphQL
188
210
  # This seems janky, but we need to know
189
211
  # the field's return type at this path in order
190
212
  # to propagate `null`
191
- 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
192
216
  # Set this before calling `run_with_directives`, so that the directive can have the latest path
193
217
  set_all_interpreter_context(nil, field_defn, nil, next_path)
194
218
 
@@ -202,21 +226,21 @@ module GraphQL
202
226
  total_args_count = field_defn.arguments.size
203
227
  if total_args_count == 0
204
228
  kwarg_arguments = GraphQL::Execution::Interpreter::Arguments::EMPTY
205
- 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)
206
230
  else
207
231
  # TODO remove all arguments(...) usages?
208
232
  @query.arguments_cache.dataload_for(ast_node, field_defn, object) do |resolved_arguments|
209
- 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)
210
234
  end
211
235
  end
212
236
  end
213
237
 
214
- 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
215
239
  context.scoped_context = scoped_context
216
240
  return_type = field_defn.type
217
- 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|
218
242
  if resolved_arguments.is_a?(GraphQL::ExecutionError) || resolved_arguments.is_a?(GraphQL::UnauthorizedError)
219
- 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)
220
244
  next
221
245
  end
222
246
 
@@ -284,10 +308,10 @@ module GraphQL
284
308
  rescue GraphQL::ExecutionError => err
285
309
  err
286
310
  end
287
- 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|
288
- continue_value = continue_value(next_path, inner_result, owner_type, field_defn, return_type.non_null?, ast_node)
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)
289
313
  if HALT != continue_value
290
- continue_field(next_path, continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, kwarg_arguments)
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)
291
315
  end
292
316
  end
293
317
  end
@@ -304,43 +328,109 @@ module GraphQL
304
328
  end
305
329
  end
306
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
+
307
374
  HALT = Object.new
308
- def continue_value(path, value, parent_type, field, is_non_null, ast_node)
309
- 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
310
378
  if is_non_null
311
- err = parent_type::InvalidNullError.new(parent_type, field, value)
312
- 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
313
384
  else
314
- write_in_response(path, nil)
385
+ set_result(selection_result, result_name, nil)
315
386
  end
316
387
  HALT
317
- elsif value.is_a?(GraphQL::ExecutionError)
318
- value.path ||= path
319
- value.ast_node ||= ast_node
320
- write_execution_errors_in_response(path, [value])
321
- HALT
322
- elsif value.is_a?(Array) && value.any? && value.all? { |v| v.is_a?(GraphQL::ExecutionError) }
323
- value.each_with_index do |error, index|
324
- error.ast_node ||= ast_node
325
- 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
326
415
  end
327
- write_execution_errors_in_response(path, value)
328
- HALT
329
- elsif value.is_a?(GraphQL::UnauthorizedError)
330
- # this hook might raise & crash, or it might return
331
- # a replacement value
332
- next_value = begin
333
- schema.unauthorized_object(value)
334
- rescue GraphQL::ExecutionError => err
335
- 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
336
430
  end
337
-
338
- continue_value(path, next_value, parent_type, field, is_non_null, ast_node)
339
- elsif GraphQL::Execution::Execute::SKIP == value
340
- HALT
341
- elsif value.is_a?(GraphQL::Execution::Interpreter::RawValue)
431
+ when GraphQL::Execution::Interpreter::RawValue
342
432
  # Write raw value directly to the response without resolving nested objects
343
- write_in_response(path, value.resolve)
433
+ set_result(selection_result, result_name, value.resolve)
344
434
  HALT
345
435
  else
346
436
  value
@@ -355,17 +445,22 @@ module GraphQL
355
445
  # Location information from `path` and `ast_node`.
356
446
  #
357
447
  # @return [Lazy, Array, Hash, Object] Lazy, Array, and Hash are all traversed to resolve lazy values later
358
- 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
+
359
454
  case current_type.kind.name
360
455
  when "SCALAR", "ENUM"
361
456
  r = current_type.coerce_result(value, context)
362
- write_in_response(path, r)
457
+ set_result(selection_result, result_name, r)
363
458
  r
364
459
  when "UNION", "INTERFACE"
365
460
  resolved_type_or_lazy, resolved_value = resolve_type(current_type, value, path)
366
461
  resolved_value ||= value
367
462
 
368
- 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|
369
464
  possible_types = query.possible_types(current_type)
370
465
 
371
466
  if !possible_types.include?(resolved_type)
@@ -373,10 +468,10 @@ module GraphQL
373
468
  err_class = current_type::UnresolvedTypeError
374
469
  type_error = err_class.new(resolved_value, field, parent_type, resolved_type, possible_types)
375
470
  schema.type_error(type_error, context)
376
- write_in_response(path, nil)
471
+ set_result(selection_result, result_name, nil)
377
472
  nil
378
473
  else
379
- 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)
380
475
  end
381
476
  end
382
477
  when "OBJECT"
@@ -385,34 +480,40 @@ module GraphQL
385
480
  rescue GraphQL::ExecutionError => err
386
481
  err
387
482
  end
388
- 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|
389
- 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)
390
485
  if HALT != continue_value
391
- response_hash = {}
392
- 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)
393
490
  gathered_selections = gather_selections(continue_value, current_type, next_selections)
394
- 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)
395
492
  response_hash
396
493
  end
397
494
  end
398
495
  when "LIST"
399
- response_list = []
400
- write_in_response(path, response_list)
401
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
+
402
503
  idx = 0
403
504
  scoped_context = context.scoped_context
404
505
  begin
405
506
  value.each do |inner_value|
406
507
  next_path = path.dup
407
508
  next_path << idx
509
+ this_idx = idx
408
510
  next_path.freeze
409
511
  idx += 1
410
- set_type_at_path(next_path, inner_type)
411
512
  # This will update `response_list` with the lazy
412
- 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|
413
- 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)
414
515
  if HALT != continue_value
415
- 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)
416
517
  end
417
518
  end
418
519
  end
@@ -428,11 +529,6 @@ module GraphQL
428
529
  end
429
530
 
430
531
  response_list
431
- when "NON_NULL"
432
- inner_type = current_type.of_type
433
- # Don't `set_type_at_path` because we want the static type,
434
- # we're going to use that to determine whether a `nil` should be propagated or not.
435
- continue_field(path, value, owner_type, field, inner_type, ast_node, next_selections, true, owner_object, arguments)
436
532
  else
437
533
  raise "Invariant: Unhandled type kind #{current_type.kind} (#{current_type})"
438
534
  end
@@ -492,9 +588,8 @@ module GraphQL
492
588
  # @param eager [Boolean] Set to `true` for mutation root fields only
493
589
  # @param trace [Boolean] If `false`, don't wrap this with field tracing
494
590
  # @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.
495
- def after_lazy(lazy_obj, owner:, field:, path:, scoped_context:, owner_object:, arguments:, ast_node:, eager: false, trace: true, &block)
496
- set_all_interpreter_context(owner_object, field, arguments, path)
497
- 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)
498
593
  lazy = GraphQL::Execution::Lazy.new(path: path, field: field) do
499
594
  set_all_interpreter_context(owner_object, field, arguments, path)
500
595
  context.scoped_context = scoped_context
@@ -513,16 +608,17 @@ module GraphQL
513
608
  rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => err
514
609
  err
515
610
  end
516
- 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)
517
612
  end
518
613
 
519
614
  if eager
520
615
  lazy.value
521
616
  else
522
- write_in_response(path, lazy)
617
+ set_result(result, result_name, lazy)
523
618
  lazy
524
619
  end
525
620
  else
621
+ set_all_interpreter_context(owner_object, field, arguments, path)
526
622
  yield(lazy_obj)
527
623
  end
528
624
  end
@@ -536,85 +632,6 @@ module GraphQL
536
632
  end
537
633
  end
538
634
 
539
- def write_invalid_null_in_response(path, invalid_null_error)
540
- if !dead_path?(path)
541
- schema.type_error(invalid_null_error, context)
542
- write_in_response(path, nil)
543
- add_dead_path(path)
544
- end
545
- end
546
-
547
- def write_execution_errors_in_response(path, errors)
548
- if !dead_path?(path)
549
- errors.each do |v|
550
- context.errors << v
551
- end
552
- write_in_response(path, nil)
553
- add_dead_path(path)
554
- end
555
- end
556
-
557
- def write_in_response(path, value)
558
- if dead_path?(path)
559
- return
560
- else
561
- if value.nil? && path.any? && type_at(path).non_null?
562
- # This nil is invalid, try writing it at the previous spot
563
- propagate_path = path[0..-2]
564
- write_in_response(propagate_path, value)
565
- add_dead_path(propagate_path)
566
- else
567
- @response.write(path, value)
568
- end
569
- end
570
- end
571
-
572
- def value_at(path)
573
- i = 0
574
- value = @response.final_value
575
- while value && (part = path[i])
576
- value = value[part]
577
- i += 1
578
- end
579
- value
580
- end
581
-
582
- # To propagate nulls, we have to know what the field type was
583
- # at previous parts of the response.
584
- # This hash matches the response
585
- def type_at(path)
586
- @types_at_paths.fetch(path)
587
- end
588
-
589
- def set_type_at_path(path, type)
590
- @types_at_paths[path] = type
591
- nil
592
- end
593
-
594
- # Mark `path` as having been permanently nulled out.
595
- # No values will be added beyond that path.
596
- def add_dead_path(path)
597
- dead = @dead_paths
598
- path.each do |part|
599
- dead = dead[part] ||= {}
600
- end
601
- dead[:__dead] = true
602
- end
603
-
604
- def dead_path?(path)
605
- res = @dead_paths
606
- path.each do |part|
607
- if res
608
- if res[:__dead]
609
- break
610
- else
611
- res = res[part]
612
- end
613
- end
614
- end
615
- res && res[:__dead]
616
- end
617
-
618
635
  # Set this pair in the Query context, but also in the interpeter namespace,
619
636
  # for compatibility.
620
637
  def set_interpreter_context(key, value)
@@ -633,7 +650,7 @@ module GraphQL
633
650
  query.resolve_type(type, value)
634
651
  end
635
652
 
636
- if schema.lazy?(resolved_type)
653
+ if lazy?(resolved_type)
637
654
  GraphQL::Execution::Lazy.new do
638
655
  query.trace("resolve_type_lazy", trace_payload) do
639
656
  schema.sync_lazy(resolved_type)
@@ -647,6 +664,12 @@ module GraphQL
647
664
  def authorized_new(type, value, context)
648
665
  type.authorized_new(value, context)
649
666
  end
667
+
668
+ def lazy?(object)
669
+ @lazy_cache.fetch(object.class) {
670
+ @lazy_cache[object.class] = @schema.lazy?(object)
671
+ }
672
+ end
650
673
  end
651
674
  end
652
675
  end