graphql 1.12.10 → 1.12.11

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