graphql 2.6.3 → 2.6.10

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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/field_extractor.rb +17 -1
  3. data/lib/generators/graphql/templates/schema.erb +2 -1
  4. data/lib/graphql/analysis/query_complexity.rb +2 -2
  5. data/lib/graphql/dashboard/application_controller.rb +5 -1
  6. data/lib/graphql/dashboard.rb +3 -0
  7. data/lib/graphql/dataloader/async_dataloader.rb +333 -68
  8. data/lib/graphql/dataloader/source.rb +32 -19
  9. data/lib/graphql/dataloader.rb +52 -36
  10. data/lib/graphql/execution/field_resolve_step.rb +79 -19
  11. data/lib/graphql/execution/finalize.rb +7 -8
  12. data/lib/graphql/execution/interpreter/runtime.rb +1 -8
  13. data/lib/graphql/execution/interpreter.rb +9 -1
  14. data/lib/graphql/execution/prepare_object_step.rb +8 -4
  15. data/lib/graphql/execution/runner.rb +17 -5
  16. data/lib/graphql/execution/selections_step.rb +10 -6
  17. data/lib/graphql/execution_error.rb +4 -0
  18. data/lib/graphql/float_decoding_error.rb +13 -0
  19. data/lib/graphql/float_encoding_error.rb +28 -0
  20. data/lib/graphql/language/block_string.rb +6 -11
  21. data/lib/graphql/language/cache.rb +84 -14
  22. data/lib/graphql/language/lexer.rb +15 -9
  23. data/lib/graphql/language/nodes.rb +2 -1
  24. data/lib/graphql/language.rb +3 -3
  25. data/lib/graphql/pagination/relation_connection.rb +10 -1
  26. data/lib/graphql/query/variable_validation_error.rb +16 -1
  27. data/lib/graphql/query/variables.rb +1 -1
  28. data/lib/graphql/railtie.rb +2 -1
  29. data/lib/graphql/schema/build_from_definition.rb +11 -3
  30. data/lib/graphql/schema/directive.rb +3 -0
  31. data/lib/graphql/schema/input_object.rb +2 -2
  32. data/lib/graphql/schema/resolver.rb +1 -1
  33. data/lib/graphql/schema/wrapper.rb +4 -0
  34. data/lib/graphql/schema.rb +5 -5
  35. data/lib/graphql/static_validation/rules/fields_will_merge.rb +66 -29
  36. data/lib/graphql/subscriptions/serialize.rb +10 -4
  37. data/lib/graphql/types/float.rb +18 -4
  38. data/lib/graphql/version.rb +1 -1
  39. data/lib/graphql.rb +2 -0
  40. metadata +4 -58
@@ -58,7 +58,9 @@ module GraphQL
58
58
  end
59
59
 
60
60
  def initialize(nonblocking: self.class.default_nonblocking, fiber_limit: self.class.default_fiber_limit)
61
- @source_cache = Hash.new { |h, k| h[k] = {} }
61
+ @source_cache = Hash.new { |h, k| h[k] = {} }.compare_by_identity
62
+ @pending_source_set = Set.new.compare_by_identity
63
+ @pending_sources = []
62
64
  @pending_jobs = []
63
65
  if !nonblocking.nil?
64
66
  @nonblocking = nonblocking
@@ -108,7 +110,7 @@ module GraphQL
108
110
  # @param batch_parameters [Array<Object>]
109
111
  # @return [GraphQL::Dataloader::Source] An instance of {source_class}, initialized with `self, *batch_parameters`,
110
112
  # and cached for the lifetime of this {Multiplex}.
111
- if (RUBY_ENGINE == "ruby" && RUBY_ENGINE < "3") || RUBY_ENGINE == "truffleruby" # truffle-ruby wasn't doing well with the implementation below
113
+ if (RUBY_ENGINE == "ruby" && RUBY_VERSION < "3") || RUBY_ENGINE == "truffleruby" # truffle-ruby wasn't doing well with the implementation below
112
114
  def with(source_class, *batch_args)
113
115
  batch_key = source_class.batch_key_for(*batch_args)
114
116
  @source_cache[source_class][batch_key] ||= begin
@@ -148,6 +150,14 @@ module GraphQL
148
150
  nil
149
151
  end
150
152
 
153
+ # @api private
154
+ def queue_pending_source(source)
155
+ if @pending_source_set.add?(source)
156
+ @pending_sources << source
157
+ end
158
+ nil
159
+ end
160
+
151
161
  # Clear any already-loaded objects from {Source} caches
152
162
  # @return [void]
153
163
  def clear_cache
@@ -187,9 +197,10 @@ module GraphQL
187
197
  @lazies_at_depth = prev_lazies_at_depth
188
198
  prev_pending_keys.each do |source_instance, pending|
189
199
  pending.each do |key, value|
190
- if !source_instance.results.key?(key)
191
- source_instance.pending[key] = value
192
- end
200
+ next if source_instance.results.key?(key)
201
+
202
+ queue_pending_source(source_instance) if source_instance.pending.empty?
203
+ source_instance.pending[key] = value
193
204
  end
194
205
  end
195
206
  end
@@ -212,8 +223,10 @@ module GraphQL
212
223
 
213
224
  if !@lazies_at_depth.empty?
214
225
  with_trace_query_lazy(trace_query_lazy) do
215
- run_next_pending_lazies(job_fibers, trace)
216
- run_pending_steps(trace, job_fibers, next_job_fibers, jobs_fiber_limit, source_fibers, next_source_fibers, total_fiber_limit)
226
+ if enqueue_next_pending_lazies(@lazies_at_depth)
227
+ job_fibers.unshift(spawn_job_fiber(trace))
228
+ run_pending_steps(trace, job_fibers, next_job_fibers, jobs_fiber_limit, source_fibers, next_source_fibers, total_fiber_limit)
229
+ end
217
230
  end
218
231
  end
219
232
  end
@@ -274,24 +287,19 @@ module GraphQL
274
287
 
275
288
  private
276
289
 
277
- def run_next_pending_lazies(job_fibers, trace)
278
- smallest_depth = nil
279
- @lazies_at_depth.each_key do |depth_key|
280
- smallest_depth ||= depth_key
281
- if depth_key < smallest_depth
282
- smallest_depth = depth_key
283
- end
284
- end
290
+ # Returns true if anything was actually enqueued
291
+ def enqueue_next_pending_lazies(lazies_at_depth)
292
+ smallest_depth = lazies_at_depth.each_key.min
293
+ return false if smallest_depth.nil?
285
294
 
286
- if smallest_depth
287
- lazies = @lazies_at_depth.delete(smallest_depth)
288
- if !lazies.empty?
289
- lazies.each_with_index do |l, idx|
290
- append_job { l.value }
291
- end
292
- job_fibers.unshift(spawn_job_fiber(trace))
293
- end
295
+ lazies = lazies_at_depth.delete(smallest_depth)
296
+ return false if lazies.empty?
297
+
298
+ lazies.each do |lazy|
299
+ append_job { lazy.value }
294
300
  end
301
+
302
+ true
295
303
  end
296
304
 
297
305
  def run_pending_steps(trace, job_fibers, next_job_fibers, jobs_fiber_limit, source_fibers, next_source_fibers, total_fiber_limit)
@@ -305,7 +313,7 @@ module GraphQL
305
313
  end
306
314
  join_queues(job_fibers, next_job_fibers)
307
315
 
308
- while (!source_fibers.empty? || @source_cache.each_value.any? { |group_sources| group_sources.each_value.any?(&:pending?) })
316
+ while (!source_fibers.empty? || !@pending_sources.empty?)
309
317
  while (f = source_fibers.shift || (((job_fibers.size + source_fibers.size + next_source_fibers.size + next_job_fibers.size) < total_fiber_limit) && spawn_source_fiber(trace)))
310
318
  if f.alive?
311
319
  finished = run_fiber(f)
@@ -356,21 +364,29 @@ module GraphQL
356
364
  end
357
365
  end
358
366
 
359
- def spawn_source_fiber(trace)
360
- pending_sources = nil
361
- @source_cache.each_value do |source_by_batch_params|
362
- source_by_batch_params.each_value do |source|
363
- if source.pending?
364
- pending_sources ||= []
365
- pending_sources << source
366
- end
367
- end
367
+ def drain_pending_sources
368
+ pending_sources = @pending_sources
369
+ @pending_sources = []
370
+ @pending_source_set.clear
371
+
372
+ pending_sources.select!(&:pending?)
373
+ pending_sources.empty? ? nil : pending_sources
374
+ end
375
+
376
+ def dequeue_pending_source
377
+ while (source = @pending_sources.shift)
378
+ @pending_source_set.delete(source)
379
+ return source if source.pending?
368
380
  end
381
+ end
369
382
 
370
- if pending_sources
383
+ def spawn_source_fiber(trace)
384
+ if !@pending_sources.empty?
371
385
  spawn_fiber do
372
- trace&.dataloader_spawn_source_fiber(pending_sources)
373
- pending_sources.each do |source|
386
+ trace&.dataloader_spawn_source_fiber(@pending_sources)
387
+ # This will find sources which were enqueued during `#fetch`:
388
+ while (source = dequeue_pending_source)
389
+ next if !source.pending?
374
390
  Fiber[:__graphql_current_dataloader_source] = source
375
391
  trace&.begin_dataloader_source(source)
376
392
  source.run_pending_keys
@@ -49,11 +49,12 @@ module GraphQL
49
49
  end
50
50
 
51
51
  def value
52
+ return nil if @selections_step.killed
52
53
  query = @selections_step.query
53
54
  set_current_field
54
- query.current_trace.begin_execute_field(@field_definition, @arguments, @field_results, query)
55
+ query.current_trace.begin_execute_field(@field_definition, @field_results, @arguments, query)
55
56
  sync(@field_results)
56
- query.current_trace.end_execute_field(@field_definition, @arguments, @field_results, query, @field_results)
57
+ query.current_trace.end_execute_field(@field_definition, @field_results, @arguments, query, @field_results)
57
58
  @runner.add_step(self)
58
59
  true
59
60
  ensure
@@ -79,7 +80,9 @@ module GraphQL
79
80
  end
80
81
 
81
82
  def call
83
+ return nil if @selections_step.killed
82
84
  set_current_field if @field_definition
85
+
83
86
  if @enqueued_authorization
84
87
  enqueue_next_steps
85
88
  elsif @finish_extension_idx
@@ -102,24 +105,64 @@ module GraphQL
102
105
  set_current_field(nil)
103
106
  end
104
107
 
105
- def add_graphql_error(err)
108
+ def add_graphql_error(result, key, err, return_type: @field_definition.type)
106
109
  err.path = path
107
110
  if err.ast_node.nil?
108
111
  err.ast_nodes = ast_nodes
109
112
  end
110
- @selections_step.query.context.add_error(err)
113
+ @runner.add_finalizer(@selections_step.query, result, key, err)
114
+ if !err.is_a?(GraphQL::Execution::Skip)
115
+ field_type = return_type
116
+ should_propagate_null = field_type.non_null?
117
+ while (should_propagate_null == false && field_type.kind.wraps?)
118
+ field_type = field_type.of_type
119
+ should_propagate_null = field_type.non_null?
120
+ end
121
+ if should_propagate_null
122
+ propagate_nulls
123
+ end
124
+ end
111
125
  err
112
126
  end
113
127
 
128
+ def propagate_nulls
129
+ propagating_null = true
130
+ highest_nulled_depth = path.size
131
+ highest_list_depth = nil
132
+ current_field_step = self
133
+ while current_field_step && current_field_step.field_definition
134
+ return_type = current_field_step.field_definition.type
135
+ if propagating_null && return_type.non_null?
136
+ highest_nulled_depth = current_field_step.path.size
137
+ else
138
+ propagating_null = false
139
+ end
140
+
141
+ if return_type.list?
142
+ highest_list_depth = current_field_step.path.size
143
+ end
144
+
145
+ current_field_step = current_field_step.selections_step.field_resolve_step
146
+ end
147
+
148
+ if highest_list_depth.nil? || highest_nulled_depth <= highest_list_depth
149
+ kill_field_step = self
150
+ while kill_field_step && kill_field_step.field_definition && highest_nulled_depth <= kill_field_step.path.size
151
+ kill_field_step.selections_step.killed = true
152
+ kill_field_step = kill_field_step.selections_step.field_resolve_step
153
+ end
154
+ end
155
+ end
156
+
114
157
  def build_errors_result(errors, single_error)
115
158
  first_error = errors.nil? ? single_error : errors.pop
116
- @field_results = error_instance_array(@selections_step.objects.size, first_error)
159
+ @field_results = [first_error]
160
+ @results = [@selections_step.results.first]
117
161
  if errors
118
162
  errors.each do |e|
119
- add_graphql_error(e)
163
+ add_graphql_error(@results.first, key, e)
120
164
  end
121
165
  end
122
- @results ||= @selections_step.results
123
166
  build_results
124
167
  end
125
168
 
@@ -226,7 +269,7 @@ module GraphQL
226
269
  authorized_results << @results[i]
227
270
  end
228
271
  rescue GraphQL::ExecutionError => exec_err
229
- add_graphql_error(exec_err)
272
+ add_graphql_error(@results[i], key, exec_err)
230
273
  end
231
274
  end
232
275
  i += 1
@@ -244,7 +287,7 @@ module GraphQL
244
287
  @was_scoped = true
245
288
  end
246
289
 
247
- query.current_trace.begin_execute_field(@field_definition, @arguments, authorized_objects, query)
290
+ query.current_trace.begin_execute_field(@field_definition, authorized_objects, @arguments, query)
248
291
 
249
292
  if @runner.uses_runtime_directives
250
293
  if @ast_nodes.nil? || @ast_nodes.size == 1
@@ -319,7 +362,7 @@ module GraphQL
319
362
  @field_results = resolve_batch(authorized_objects, ctx, @arguments)
320
363
  end
321
364
 
322
- query.current_trace.end_execute_field(@field_definition, @arguments, authorized_objects, query, @field_results)
365
+ query.current_trace.end_execute_field(@field_definition, authorized_objects, @arguments, query, @field_results)
323
366
 
324
367
  if any_lazy_results?
325
368
  @runner.dataloader.lazy_at_depth(path.size, self)
@@ -331,12 +374,12 @@ module GraphQL
331
374
  end
332
375
  end
333
376
  rescue GraphQL::ExecutionError => err
334
- add_graphql_error(err)
377
+ build_errors_result(nil, err)
335
378
  rescue StandardError => stderr
336
379
  begin
337
380
  @selections_step.query.handle_or_reraise(stderr, field: @field_definition, arguments: @arguments, object: nil)
338
381
  rescue GraphQL::ExecutionError => err
339
- add_graphql_error(err)
382
+ add_graphql_error(@results[0], key, err)
340
383
  end
341
384
  end
342
385
 
@@ -458,13 +501,13 @@ module GraphQL
458
501
  end
459
502
 
460
503
  def finish_leaf_result(result_h, key, field_result, return_type, ctx)
461
- final_field_result = build_leaf_result(field_result, return_type, ctx, false)
504
+ final_field_result = build_leaf_result(result_h, key, field_result, return_type, ctx, false)
462
505
 
463
506
  @directive_finalizers&.each { |f| @runner.add_finalizer(ctx.query, result_h, key, f) }
464
507
  result_h[@key] = final_field_result
465
508
  end
466
509
 
467
- def build_leaf_result(field_result, return_type, ctx, is_from_array)
510
+ def build_leaf_result(result_h, result_key, field_result, return_type, ctx, is_from_array)
468
511
  if field_result.nil?
469
512
  if return_type.non_null?
470
513
  add_non_null_error(is_from_array)
@@ -473,7 +516,7 @@ module GraphQL
473
516
  end
474
517
  elsif field_result.is_a?(Finalizer)
475
518
  if field_result.is_a?(GraphQL::RuntimeError)
476
- add_graphql_error(field_result)
519
+ add_graphql_error(result_h, result_key, field_result, return_type: return_type)
477
520
  else
478
521
  field_result.path = path
479
522
  @runner.add_finalizer(ctx.query, result_h, key, field_result)
@@ -484,7 +527,11 @@ module GraphQL
484
527
  end
485
528
 
486
529
  inner_type = return_type.of_type
487
- field_result.map { |item| build_leaf_result(item, inner_type, ctx, true) }
530
+ result_a = Array.new(field_result.size)
531
+ field_result.each_with_index do |item, idx|
532
+ result_a[idx] = build_leaf_result(result_a, idx, item, inner_type, ctx, true)
533
+ end
534
+ result_a
488
535
  else
489
536
  return_type.coerce_result(field_result, ctx)
490
537
  end
@@ -526,6 +573,7 @@ module GraphQL
526
573
  query.current_trace.objects(obj_type, next_objects, ctx)
527
574
  @runner.add_step(SelectionsStep.new(
528
575
  path: path,
576
+ field_resolve_step: self,
529
577
  parent_type: obj_type,
530
578
  selections: @next_selections,
531
579
  objects: next_objects,
@@ -538,6 +586,7 @@ module GraphQL
538
586
  query.current_trace.objects(@static_type, @all_next_objects, ctx)
539
587
  @runner.add_step(SelectionsStep.new(
540
588
  path: path,
589
+ field_resolve_step: self,
541
590
  parent_type: @static_type,
542
591
  selections: @next_selections,
543
592
  objects: @all_next_objects,
@@ -558,7 +607,11 @@ module GraphQL
558
607
 
559
608
  def add_non_null_error(is_from_array)
560
609
  err = @parent_type::InvalidNullError.new(@parent_type, @field_definition, ast_nodes, is_from_array: is_from_array, path: path)
561
- @runner.schema.type_error(err, @selections_step.query.context)
610
+ nn_result = @runner.schema.type_error(err, @selections_step.query.context)
611
+ if nn_result.nil?
612
+ propagate_nulls
613
+ end
614
+ nn_result
562
615
  end
563
616
 
564
617
  def set_current_field(new_value = @field_definition)
@@ -576,7 +629,7 @@ module GraphQL
576
629
  end
577
630
  elsif field_result.is_a?(Finalizer)
578
631
  graphql_result[key] = if field_result.is_a?(GraphQL::RuntimeError)
579
- add_graphql_error(field_result)
632
+ add_graphql_error(graphql_result, key, field_result)
580
633
  else
581
634
  field_result.path = path
582
635
  @runner.add_finalizer(@selections_step.query, graphql_result, key, field_result)
@@ -691,7 +744,8 @@ module GraphQL
691
744
  when :dig
692
745
  objects.map { |o| o.dig(*@field_definition.execution_mode_key) }
693
746
  when :dataload
694
- if (k = @field_definition.execution_mode_key).is_a?(Class)
747
+ k = @field_definition.execution_mode_key
748
+ results = if k.is_a?(Class)
695
749
  context.dataload_all(k, objects)
696
750
  elsif (source_class = k[:with])
697
751
  if (batch_args = k[:by])
@@ -711,6 +765,12 @@ module GraphQL
711
765
  else
712
766
  raise ArgumentError, "Unexpected `dataload: ...` configuration: #{k.inspect}"
713
767
  end
768
+ method = k.is_a?(Hash) ? k[:method] : nil
769
+ if method
770
+ results.map { |r| r&.public_send(method) }
771
+ else
772
+ results
773
+ end
714
774
  when :resolver_class
715
775
  results = Array.new(objects.size, nil)
716
776
  ps = @pending_steps ||= []
@@ -33,14 +33,15 @@ module GraphQL
33
33
 
34
34
  targets.each_with_index do |target, idx|
35
35
  if target.is_a?(Hash)
36
- if target[key].equal?(err)
36
+ value_at_key = target[key]
37
+ if value_at_key.equal?(err)
37
38
  tf = @finalizers[target] ||= {}.compare_by_identity
38
39
  tf[key] = err
39
40
  @finalizers_count += 1
40
- elsif (arr = target[key]).is_a?(Array)
41
- arr.each_with_index do |el, idx|
41
+ elsif value_at_key.is_a?(Array)
42
+ value_at_key.each_with_index do |el, idx|
42
43
  if el.equal?(err)
43
- tf = @finalizers[arr] ||= {}.compare_by_identity
44
+ tf = @finalizers[value_at_key] ||= {}.compare_by_identity
44
45
  tf[idx] = err
45
46
  @finalizers_count += 1
46
47
  end
@@ -150,7 +151,7 @@ module GraphQL
150
151
  when Language::Nodes::InlineFragment
151
152
  static_type_at_result = @static_type_at[result_h]
152
153
  if static_type_at_result && (
153
- (t = ast_selection.type).nil? ||
154
+ (t = ast_selection.type).nil? ||
154
155
  @runner.type_condition_applies?(@query.context, static_type_at_result, t.name)
155
156
  )
156
157
  result_h = check_object_result(result_h, parent_type, ast_selection.selections)
@@ -170,9 +171,7 @@ module GraphQL
170
171
  end
171
172
 
172
173
  def check_list_result(result_arr, inner_type, ast_selections)
173
- inner_type_non_null = false
174
- if inner_type.non_null?
175
- inner_type_non_null = true
174
+ if (inner_type_non_null = inner_type.non_null?)
176
175
  inner_type = inner_type.of_type
177
176
  end
178
177
 
@@ -492,12 +492,6 @@ module GraphQL
492
492
  end
493
493
  end
494
494
  end
495
- # If this field is a root mutation field, immediately resolve
496
- # all of its child fields before moving on to the next root mutation field.
497
- # (Subselections of this mutation will still be resolved level-by-level.)
498
- if selection_result.graphql_is_eager
499
- @dataloader.run
500
- end
501
495
  end
502
496
 
503
497
  def set_result(selection_result, result_name, value, is_child_result, is_non_null)
@@ -771,8 +765,7 @@ module GraphQL
771
765
 
772
766
  response_list
773
767
  rescue NoMethodError => err
774
- # Ruby 2.2 doesn't have NoMethodError#receiver, can't check that one in this case. (It's been EOL since 2017.)
775
- if err.name == :each && (err.respond_to?(:receiver) ? err.receiver == value : true)
768
+ if err.name == :each && err.receiver == value
776
769
  # This happens when the GraphQL schema doesn't match the implementation. Help the dev debug.
777
770
  raise ListResultFailedError.new(value: value, field: field, path: current_path)
778
771
  else
@@ -22,6 +22,7 @@ module GraphQL
22
22
  # @param max_complexity [Integer, nil]
23
23
  # @return [Array<GraphQL::Query::Result>] One result per query
24
24
  def run_all(schema, query_options, context: {}, max_complexity: schema.max_complexity)
25
+ previous_multiplex = Fiber[:__graphql_current_multiplex]
25
26
  queries = query_options.map do |opts|
26
27
  query = case opts
27
28
  when Hash
@@ -119,13 +120,18 @@ module GraphQL
119
120
  end
120
121
 
121
122
  results
123
+ rescue SystemStackError => err
124
+ queries.map do |query|
125
+ schema.query_stack_error(query, err)
126
+ query.result_values ||= { "errors" => query.context.errors.map(&:to_h) }
127
+ query.result
128
+ end
122
129
  rescue Exception
123
130
  # TODO rescue at a higher level so it will catch errors in analysis, too
124
131
  # Assign values here so that the query's `@executed` becomes true
125
132
  queries.map { |q| q.result_values ||= {} }
126
133
  raise
127
134
  ensure
128
- Fiber[:__graphql_current_multiplex] = nil
129
135
  queries.map { |query|
130
136
  runtime = query.context.namespace(:interpreter_runtime)[:runtime]
131
137
  if runtime
@@ -134,6 +140,8 @@ module GraphQL
134
140
  }
135
141
  end
136
142
  end
143
+ ensure
144
+ Fiber[:__graphql_current_multiplex] = previous_multiplex
137
145
  end
138
146
  end
139
147
 
@@ -73,6 +73,10 @@ module GraphQL
73
73
  @field_resolve_step.set_current_field(nil)
74
74
  end
75
75
 
76
+ def add_field_error(err)
77
+ @field_resolve_step.add_graphql_error(@graphql_result, @key, err)
78
+ end
79
+
76
80
  def authorize
77
81
  if @field_resolve_step.was_scoped && !@resolved_type.reauthorize_scoped_objects
78
82
  @authorized_value = @object
@@ -96,13 +100,13 @@ module GraphQL
96
100
  create_result
97
101
  end
98
102
  rescue GraphQL::RuntimeError => err
99
- @graphql_result[@key] = @field_resolve_step.add_graphql_error(err)
103
+ @graphql_result[@key] = add_field_error(err)
100
104
  rescue StandardError => err
101
105
  query ||= @field_resolve_step.selections_step.query
102
106
  begin
103
107
  query.handle_or_reraise(err, field: @field_resolve_step.field_definition, arguments: @field_resolve_step.arguments, object: @object) # rubocop:disable Development/ContextIsPassedCop
104
108
  rescue GraphQL::RuntimeError => err
105
- @graphql_result[@key] = @field_resolve_step.add_graphql_error(err)
109
+ @graphql_result[@key] = add_field_error(err)
106
110
  end
107
111
  end
108
112
 
@@ -120,13 +124,13 @@ module GraphQL
120
124
  elsif @is_non_null
121
125
  @graphql_result[@key] = @field_resolve_step.add_non_null_error(@is_from_array)
122
126
  else
123
- @graphql_result[@key] = @field_resolve_step.add_graphql_error(@authorization_error)
127
+ @graphql_result[@key] = add_field_error(@authorization_error)
124
128
  end
125
129
  rescue GraphQL::RuntimeError => err
126
130
  if @is_non_null
127
131
  @graphql_result[@key] = @field_resolve_step.add_non_null_error(@is_from_array)
128
132
  else
129
- @graphql_result[@key] = @field_resolve_step.add_graphql_error(err)
133
+ @graphql_result[@key] = add_field_error(err)
130
134
  end
131
135
  end
132
136
  end
@@ -5,7 +5,6 @@ module GraphQL
5
5
  def initialize(multiplex)
6
6
  @multiplex = multiplex
7
7
  @schema = multiplex.schema
8
- @steps_queue = []
9
8
  @runtime_type_at = {}.compare_by_identity
10
9
  @static_type_at = {}.compare_by_identity
11
10
  @finalizers = nil
@@ -53,7 +52,7 @@ module GraphQL
53
52
  @dataloader.append_job(step)
54
53
  end
55
54
 
56
- attr_reader :steps_queue, :schema, :variables, :dataloader, :resolves_lazies, :authorizes, :static_type_at, :runtime_type_at, :finalizers, :input_values
55
+ attr_reader :schema, :variables, :dataloader, :resolves_lazies, :authorizes, :static_type_at, :runtime_type_at, :finalizers, :input_values
57
56
 
58
57
  # @return [void]
59
58
  def add_finalizer(query, result_value, key, finalizer)
@@ -73,6 +72,7 @@ module GraphQL
73
72
  end
74
73
 
75
74
  def execute
75
+ previous_multiplex = Fiber[:__graphql_current_multiplex]
76
76
  Fiber[:__graphql_current_multiplex] = @multiplex
77
77
  isolated_steps = [[]]
78
78
  trace = @multiplex.current_trace
@@ -156,8 +156,14 @@ module GraphQL
156
156
  query.result
157
157
  end
158
158
  end
159
+ rescue SystemStackError => err
160
+ @multiplex.queries.map do |query|
161
+ @schema.query_stack_error(query, err)
162
+ query.result_values ||= { "errors" => query.context.errors.map(&:to_h) }
163
+ query.result
164
+ end
159
165
  ensure
160
- Fiber[:__graphql_current_multiplex] = nil
166
+ Fiber[:__graphql_current_multiplex] = previous_multiplex
161
167
  end
162
168
 
163
169
  def gather_selections(type_defn, ast_selections, selections_step, query, all_selections, prototype_result, into:)
@@ -281,8 +287,8 @@ module GraphQL
281
287
  end
282
288
  result = dir_defn.resolve_operation(selected_operation, query, objects, dir_args, query.context)
283
289
  if result.is_a?(Finalizer)
284
- result.path = path
285
- add_finalizer(query, result, nil, data)
290
+ result.path = beginning_path
291
+ add_finalizer(query, data, nil, result)
286
292
  if result.is_a?(HaltExecution)
287
293
  continue_execution = false
288
294
  break
@@ -298,6 +304,7 @@ module GraphQL
298
304
  if query.query?
299
305
  isolated_steps[0] << SelectionsStep.new(
300
306
  parent_type: root_type,
307
+ field_resolve_step: nil,
301
308
  selections: selected_operation.selections,
302
309
  objects: objects,
303
310
  results: [data],
@@ -317,6 +324,7 @@ module GraphQL
317
324
  isolated_steps << [SelectionsStep.new(
318
325
  clobber: false, # `data` is being shared among several selections steps
319
326
  parent_type: root_type,
327
+ field_resolve_step: field_resolve_step,
320
328
  selections: field_resolve_step.ast_nodes || Array(field_resolve_step.ast_node),
321
329
  objects: objects,
322
330
  results: [data],
@@ -332,6 +340,7 @@ module GraphQL
332
340
  end
333
341
  isolated_steps[0] << SelectionsStep.new(
334
342
  parent_type: root_type,
343
+ field_resolve_step: nil,
335
344
  selections: selected_operation.selections,
336
345
  objects: objects,
337
346
  results: [data],
@@ -355,6 +364,7 @@ module GraphQL
355
364
  results << { "data" => data }
356
365
  isolated_steps[0] << SelectionsStep.new(
357
366
  parent_type: resolved_type,
367
+ field_resolve_step: nil,
358
368
  selections: selected_operation.selections,
359
369
  objects: objects,
360
370
  results: [data],
@@ -372,6 +382,7 @@ module GraphQL
372
382
  results << { "data" => list_result }
373
383
  isolated_steps[0] << SelectionsStep.new(
374
384
  parent_type: inner_type,
385
+ field_resolve_step: nil,
375
386
  selections: selected_operation.selections,
376
387
  objects: root_value,
377
388
  results: list_result,
@@ -420,6 +431,7 @@ module GraphQL
420
431
  selections = partial.ast_nodes
421
432
  dummy_ss = SelectionsStep.new(
422
433
  parent_type: nil,
434
+ field_resolve_step: nil,
423
435
  selections: selections,
424
436
  objects: nil,
425
437
  results: nil,
@@ -2,8 +2,9 @@
2
2
  module GraphQL
3
3
  module Execution
4
4
  class SelectionsStep
5
- def initialize(parent_type:, selections:, objects:, results:, runner:, query:, path:, clobber: true)
5
+ def initialize(parent_type:, field_resolve_step:, selections:, objects:, results:, runner:, query:, path:, clobber: true)
6
6
  @path = path
7
+ @field_resolve_step = field_resolve_step
7
8
  @parent_type = parent_type
8
9
  @selections = selections
9
10
  @runner = runner
@@ -12,10 +13,13 @@ module GraphQL
12
13
  @query = query
13
14
  @graphql_objects = nil
14
15
  @all_selections = nil
16
+ @killed = false
15
17
  @clobber = clobber
16
18
  end
17
19
 
18
- attr_reader :path, :query, :objects, :results
20
+ attr_reader :path, :query, :objects, :results, :field_resolve_step
21
+
22
+ attr_accessor :killed
19
23
 
20
24
  def graphql_objects
21
25
  @graphql_objects ||= @objects.map do |obj|
@@ -79,12 +83,12 @@ module GraphQL
79
83
  end
80
84
  end
81
85
 
82
- continue_selections.each do |frs|
83
- @runner.add_step(frs)
84
- end
85
-
86
86
  i += 2
87
87
  end
88
+
89
+ continue_selections.each do |frs|
90
+ @runner.add_step(frs)
91
+ end
88
92
  end
89
93
  end
90
94
  end
@@ -26,6 +26,10 @@ module GraphQL
26
26
  end
27
27
 
28
28
  def finalize_graphql_result(query, result_data, key)
29
+ add_this_error = !query.context.errors.any? { |e| e.eql?(self) }
30
+ if add_this_error
31
+ query.context.add_error(self)
32
+ end
29
33
  if ast_node.is_a?(GraphQL::Language::Nodes::Directive)
30
34
  # This is for backwards compatibility ... what does the spec say?
31
35
  result_data.delete(key)