graphql 2.5.23 → 2.6.3

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 (79) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/analysis/query_complexity.rb +29 -13
  3. data/lib/graphql/analysis.rb +20 -13
  4. data/lib/graphql/backtrace/table.rb +10 -1
  5. data/lib/graphql/current.rb +7 -1
  6. data/lib/graphql/dataloader.rb +1 -1
  7. data/lib/graphql/execution/directive_checks.rb +2 -0
  8. data/lib/graphql/execution/field_resolve_step.rb +744 -0
  9. data/lib/graphql/execution/finalize.rb +230 -0
  10. data/lib/graphql/execution/input_values.rb +333 -0
  11. data/lib/graphql/execution/interpreter/arguments_cache.rb +3 -0
  12. data/lib/graphql/execution/interpreter/handles_raw_value.rb +6 -0
  13. data/lib/graphql/execution/interpreter/runtime.rb +36 -15
  14. data/lib/graphql/execution/load_argument_step.rb +102 -0
  15. data/lib/graphql/execution/next.rb +42 -16
  16. data/lib/graphql/execution/prepare_object_step.rb +147 -0
  17. data/lib/graphql/execution/resolve_type_step.rb +27 -0
  18. data/lib/graphql/execution/runner.rb +445 -0
  19. data/lib/graphql/execution/selections_step.rb +91 -0
  20. data/lib/graphql/execution.rb +10 -3
  21. data/lib/graphql/execution_error.rb +7 -13
  22. data/lib/graphql/introspection/entry_points.rb +2 -2
  23. data/lib/graphql/introspection/schema_type.rb +6 -2
  24. data/lib/graphql/language/lexer.rb +12 -8
  25. data/lib/graphql/language/parser.rb +1 -1
  26. data/lib/graphql/language.rb +8 -2
  27. data/lib/graphql/pagination/connections.rb +1 -3
  28. data/lib/graphql/query/context.rb +6 -0
  29. data/lib/graphql/query/partial.rb +18 -3
  30. data/lib/graphql/query.rb +12 -3
  31. data/lib/graphql/runtime_error.rb +6 -0
  32. data/lib/graphql/schema/argument.rb +3 -3
  33. data/lib/graphql/schema/build_from_definition.rb +10 -0
  34. data/lib/graphql/schema/directive/feature.rb +4 -0
  35. data/lib/graphql/schema/directive/transform.rb +20 -0
  36. data/lib/graphql/schema/directive.rb +23 -9
  37. data/lib/graphql/schema/field/connection_extension.rb +2 -15
  38. data/lib/graphql/schema/field/scope_extension.rb +0 -4
  39. data/lib/graphql/schema/field.rb +20 -20
  40. data/lib/graphql/schema/field_extension.rb +11 -41
  41. data/lib/graphql/schema/has_single_input_argument.rb +24 -13
  42. data/lib/graphql/schema/input_object.rb +4 -0
  43. data/lib/graphql/schema/interface.rb +26 -0
  44. data/lib/graphql/schema/introspection_system.rb +6 -21
  45. data/lib/graphql/schema/list.rb +4 -0
  46. data/lib/graphql/schema/member/base_dsl_methods.rb +0 -10
  47. data/lib/graphql/schema/printer.rb +1 -1
  48. data/lib/graphql/schema/ractor_shareable.rb +1 -0
  49. data/lib/graphql/schema/relay_classic_mutation.rb +16 -2
  50. data/lib/graphql/schema/resolver.rb +30 -14
  51. data/lib/graphql/schema/subscription.rb +53 -8
  52. data/lib/graphql/schema/timeout.rb +2 -2
  53. data/lib/graphql/schema/validator/allow_blank_validator.rb +3 -3
  54. data/lib/graphql/schema/validator/allow_null_validator.rb +3 -3
  55. data/lib/graphql/schema/validator/exclusion_validator.rb +2 -2
  56. data/lib/graphql/schema/validator/format_validator.rb +3 -3
  57. data/lib/graphql/schema/validator/inclusion_validator.rb +2 -2
  58. data/lib/graphql/schema/validator/length_validator.rb +6 -6
  59. data/lib/graphql/schema/validator/numericality_validator.rb +19 -19
  60. data/lib/graphql/schema/validator/required_validator.rb +6 -4
  61. data/lib/graphql/schema/validator.rb +9 -0
  62. data/lib/graphql/schema/visibility/profile.rb +6 -4
  63. data/lib/graphql/schema/visibility/visit.rb +1 -1
  64. data/lib/graphql/schema/visibility.rb +30 -22
  65. data/lib/graphql/schema.rb +43 -20
  66. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +31 -25
  67. data/lib/graphql/subscriptions/event.rb +0 -1
  68. data/lib/graphql/subscriptions.rb +15 -0
  69. data/lib/graphql/tracing/perfetto_trace.rb +5 -3
  70. data/lib/graphql/tracing/trace.rb +6 -0
  71. data/lib/graphql/unauthorized_error.rb +1 -1
  72. data/lib/graphql/version.rb +1 -1
  73. data/lib/graphql.rb +1 -3
  74. metadata +11 -7
  75. data/lib/graphql/execution/next/field_resolve_step.rb +0 -743
  76. data/lib/graphql/execution/next/load_argument_step.rb +0 -64
  77. data/lib/graphql/execution/next/prepare_object_step.rb +0 -129
  78. data/lib/graphql/execution/next/runner.rb +0 -411
  79. data/lib/graphql/execution/next/selections_step.rb +0 -37
@@ -0,0 +1,744 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module Execution
4
+ class FieldResolveStep
5
+ def initialize(parent_type:, runner:, key:, selections_step:)
6
+ @selections_step = selections_step
7
+ @key = key
8
+ @parent_type = parent_type
9
+ @ast_node = @ast_nodes = nil
10
+ @runner = runner
11
+ @field_definition = nil
12
+ @arguments = nil
13
+ @field_results = nil
14
+ @path = nil
15
+ @enqueued_authorization = false
16
+ @all_next_objects = nil
17
+ @all_next_results = nil
18
+ @static_type = nil
19
+ @next_selections = nil
20
+ @results = nil
21
+ @finish_extension_idx = nil
22
+ @was_scoped = nil
23
+ @pending_steps = nil
24
+ @arguments_without_loads = @post_processors = @directive_finalizers = nil
25
+ end
26
+
27
+ attr_reader :ast_node, :key, :parent_type, :selections_step, :runner,
28
+ :field_definition, :object_is_authorized, :was_scoped, :field_results
29
+
30
+ attr_accessor :pending_steps, :arguments, :static_type
31
+
32
+ def path
33
+ @path ||= [*@selections_step.path, @key].freeze
34
+ end
35
+
36
+ def ast_nodes
37
+ @ast_nodes ||= [@ast_node]
38
+ end
39
+
40
+ def append_selection(ast_node)
41
+ if @ast_node.nil?
42
+ @ast_node = ast_node
43
+ elsif @ast_nodes.nil?
44
+ @ast_nodes = [@ast_node, ast_node]
45
+ else
46
+ @ast_nodes << ast_node
47
+ end
48
+ nil
49
+ end
50
+
51
+ def value
52
+ query = @selections_step.query
53
+ set_current_field
54
+ query.current_trace.begin_execute_field(@field_definition, @arguments, @field_results, query)
55
+ sync(@field_results)
56
+ query.current_trace.end_execute_field(@field_definition, @arguments, @field_results, query, @field_results)
57
+ @runner.add_step(self)
58
+ true
59
+ ensure
60
+ set_current_field(nil)
61
+ end
62
+
63
+ def sync(lazy)
64
+ if lazy.is_a?(Array)
65
+ lazy.map! { |l| sync(l)}
66
+ else
67
+ @runner.schema.sync_lazy(lazy)
68
+ end
69
+ rescue GraphQL::UnauthorizedError => auth_err
70
+ @runner.schema.unauthorized_object(auth_err)
71
+ rescue GraphQL::ExecutionError => err
72
+ err
73
+ rescue StandardError => stderr
74
+ begin
75
+ @selections_step.query.handle_or_reraise(stderr, field: @field_definition, arguments: @arguments, object: nil)
76
+ rescue GraphQL::ExecutionError => ex_err
77
+ ex_err
78
+ end
79
+ end
80
+
81
+ def call
82
+ set_current_field if @field_definition
83
+ if @enqueued_authorization
84
+ enqueue_next_steps
85
+ elsif @finish_extension_idx
86
+ finish_extensions
87
+ elsif @field_results
88
+ build_results
89
+ elsif @arguments
90
+ execute_field
91
+ else
92
+ build_arguments
93
+ end
94
+ rescue StandardError => err
95
+ if @field_definition && !err.message.start_with?("Resolving ")
96
+ # TODO remove this check ^^^^^^ when NullDataloader isn't recursive
97
+ raise err, "Resolving #{@field_definition.path}: #{err.message}", err.backtrace
98
+ else
99
+ raise
100
+ end
101
+ ensure
102
+ set_current_field(nil)
103
+ end
104
+
105
+ def add_graphql_error(err)
106
+ err.path = path
107
+ if err.ast_node.nil?
108
+ err.ast_nodes = ast_nodes
109
+ end
110
+ @selections_step.query.context.add_error(err)
111
+ err
112
+ end
113
+
114
+ def build_errors_result(errors, single_error)
115
+ first_error = errors.nil? ? single_error : errors.pop
116
+ @field_results = error_instance_array(@selections_step.objects.size, first_error)
117
+ if errors
118
+ errors.each do |e|
119
+ add_graphql_error(e)
120
+ end
121
+ end
122
+ @results ||= @selections_step.results
123
+ build_results
124
+ end
125
+
126
+ def build_arguments
127
+ query = @selections_step.query
128
+ field_name = @ast_node.name
129
+ @field_definition = query.types.field(@parent_type, field_name) || raise(GraphQL::Error, "No field definition found for #{@parent_type.to_type_signature}.#{ast_node.name} (at #{@ast_node.position})")
130
+ set_current_field
131
+ @arguments, errors = @runner.input_values[query].argument_values(@field_definition, @ast_node.arguments, self) # rubocop:disable Development/ContextIsPassedCop
132
+ if errors
133
+ build_errors_result(errors, nil)
134
+ return
135
+ end
136
+
137
+ if (@pending_steps.nil? || @pending_steps.size == 0) &&
138
+ @field_results.nil? # Make sure the arguments flow didn't already call through
139
+ execute_field
140
+ end
141
+ ensure
142
+ set_current_field(nil)
143
+ end
144
+
145
+ # Used for compatibility in Schema::Subscription
146
+ def arguments_without_loads
147
+ if @arguments_without_loads.nil?
148
+ @arguments_without_loads, _errors = @runner.input_values[@selections_step.query].argument_values(@field_definition, ast_node.arguments, nil)
149
+ end
150
+ @arguments_without_loads
151
+ end
152
+
153
+ def execute_field
154
+ objects = @selections_step.objects
155
+ if @arguments.is_a?(GraphQL::RuntimeError)
156
+ build_errors_result(nil, @arguments)
157
+ return
158
+ end
159
+
160
+ @results = @selections_step.results
161
+ query = @selections_step.query
162
+ ctx = query.context
163
+ if (v = @field_definition.validators).any? # rubocop:disable Development/NoneWithoutBlockCop
164
+ begin
165
+ Schema::Validator.validate!(v, nil, ctx, @arguments)
166
+ rescue GraphQL::RuntimeError => err
167
+ build_errors_result(nil, err)
168
+ return
169
+ end
170
+ end
171
+
172
+ @field_definition.extras.each do |extra|
173
+ case extra
174
+ when :lookahead
175
+ if @arguments.frozen?
176
+ @arguments = @arguments.dup
177
+ end
178
+ @arguments[:lookahead] = Execution::Lookahead.new(
179
+ query: query,
180
+ ast_nodes: ast_nodes,
181
+ field: @field_definition,
182
+ )
183
+ when :ast_node
184
+ if @arguments.frozen?
185
+ @arguments = @arguments.dup
186
+ end
187
+ @arguments[:ast_node] = ast_node
188
+ else
189
+ raise ArgumentError, "This `extra` isn't supported yet: #{extra.inspect}. Open an issue on GraphQL-Ruby to add compatibility for it."
190
+ end
191
+ end
192
+
193
+ if @field_definition.dynamic_introspection
194
+ objects = @selections_step.graphql_objects.map { |o| @field_definition.owner.wrap(o, ctx) }
195
+ end
196
+
197
+ if @runner.authorizes?(@field_definition, ctx)
198
+ authorized_objects = []
199
+ authorized_results = []
200
+ l = objects.size
201
+ i = 0
202
+ while i < l
203
+ o = objects[i]
204
+ err = nil
205
+ begin
206
+ field_authed = @field_definition.authorized?(o, @arguments, ctx)
207
+ if @runner.resolves_lazies && @runner.lazy?(field_authed)
208
+ # TODO batch this properly...
209
+ field_authed = sync(field_authed)
210
+ end
211
+ rescue GraphQL::UnauthorizedFieldError => field_auth_err
212
+ err = field_auth_err
213
+ err.field ||= @field_definition
214
+ field_authed = false
215
+ end
216
+
217
+ if field_authed
218
+ authorized_results << @results[i]
219
+ authorized_objects << o
220
+ else
221
+ begin
222
+ err ||= GraphQL::UnauthorizedFieldError.new(object: o, type: @parent_type, context: ctx, field: @field_definition)
223
+ new_obj = query.schema.unauthorized_field(err)
224
+ if !new_obj.nil?
225
+ authorized_objects << new_obj
226
+ authorized_results << @results[i]
227
+ end
228
+ rescue GraphQL::ExecutionError => exec_err
229
+ add_graphql_error(exec_err)
230
+ end
231
+ end
232
+ i += 1
233
+ end
234
+
235
+ if authorized_objects.size == 0
236
+ return
237
+ end
238
+ @results = authorized_results
239
+ else
240
+ authorized_objects = objects
241
+ end
242
+
243
+ if @parent_type.default_relay? && authorized_objects.all? { |o| o.respond_to?(:was_authorized_by_scope_items?) && o.was_authorized_by_scope_items? }
244
+ @was_scoped = true
245
+ end
246
+
247
+ query.current_trace.begin_execute_field(@field_definition, @arguments, authorized_objects, query)
248
+
249
+ if @runner.uses_runtime_directives
250
+ if @ast_nodes.nil? || @ast_nodes.size == 1
251
+ directives = if !@ast_node.directives.empty?
252
+ @ast_node.directives
253
+ else
254
+ nil
255
+ end
256
+ else
257
+ directives = nil
258
+ @ast_nodes.each do |n|
259
+ if (d = n.directives).any? # rubocop:disable Development/NoneWithoutBlockCop
260
+ directives ||= []
261
+ directives.concat(d)
262
+ end
263
+ end
264
+ end
265
+
266
+ if directives
267
+ directives.each do |dir_node|
268
+ if (dir_defn = @runner.runtime_directives[dir_node.name])
269
+ dir_args, errors = @runner.input_values[query].argument_values(dir_defn, dir_node.arguments, self) # rubocop:disable Development/ContextIsPassedCop
270
+ if errors
271
+ @results.each { |r| r.delete(@key) }
272
+ errors.each { |e| e.ast_node = dir_node }
273
+ build_errors_result(errors, nil)
274
+ return
275
+ else
276
+ begin
277
+ dir_defn.validate!(dir_args, query.context)
278
+ if !(result = dir_defn.resolve_field(ast_nodes, @parent_type, field_definition, authorized_objects, dir_args, ctx)).nil?
279
+ if result.is_a?(Finalizer)
280
+ result.path = path
281
+ @directive_finalizers ||= []
282
+ @directive_finalizers << result
283
+ end
284
+
285
+ if result.is_a?(PostProcessor)
286
+ @post_processors ||= []
287
+ @post_processors << result
288
+ end
289
+
290
+ if result.is_a?(HaltExecution)
291
+ @directive_finalizers&.each { |f|
292
+ @selections_step.results.each { |r| @runner.add_finalizer(query, r, key, f) }
293
+ }
294
+ return
295
+ end
296
+ end
297
+ rescue GraphQL::RuntimeError => err
298
+ err.ast_node = dir_node
299
+ raise
300
+ end
301
+ end
302
+ end
303
+ end
304
+ end
305
+ end
306
+
307
+ has_extensions = @field_definition.extensions.size > 0
308
+ if has_extensions
309
+ @extended = GraphQL::Schema::Field::ExtendedState.new(@arguments, authorized_objects)
310
+ @field_results = @field_definition.run_next_extensions_before_resolve(authorized_objects, @arguments, ctx, @extended) do |objs, args|
311
+ if (added_extras = @extended.added_extras)
312
+ args = args.dup
313
+ added_extras.each { |e| args.delete(e) }
314
+ end
315
+ resolve_batch(objs, ctx, args)
316
+ end
317
+ @finish_extension_idx = 0
318
+ else
319
+ @field_results = resolve_batch(authorized_objects, ctx, @arguments)
320
+ end
321
+
322
+ query.current_trace.end_execute_field(@field_definition, @arguments, authorized_objects, query, @field_results)
323
+
324
+ if any_lazy_results?
325
+ @runner.dataloader.lazy_at_depth(path.size, self)
326
+ elsif @pending_steps.nil? || @pending_steps.empty?
327
+ if has_extensions
328
+ finish_extensions
329
+ else
330
+ build_results
331
+ end
332
+ end
333
+ rescue GraphQL::ExecutionError => err
334
+ add_graphql_error(err)
335
+ rescue StandardError => stderr
336
+ begin
337
+ @selections_step.query.handle_or_reraise(stderr, field: @field_definition, arguments: @arguments, object: nil)
338
+ rescue GraphQL::ExecutionError => err
339
+ add_graphql_error(err)
340
+ end
341
+ end
342
+
343
+ def any_lazy_results?
344
+ lazies = false
345
+ if @runner.resolves_lazies # TODO extract this
346
+ @field_results.each do |field_result|
347
+ if @runner.lazy?(field_result)
348
+ lazies = true
349
+ break
350
+ elsif field_result.is_a?(Array)
351
+ field_result.each do |inner_fr|
352
+ if @runner.lazy?(inner_fr)
353
+ break lazies = true
354
+ end
355
+ end
356
+ if lazies
357
+ break
358
+ end
359
+ end
360
+ end
361
+ end
362
+ lazies
363
+ end
364
+
365
+ def finish_extensions
366
+ ctx = @selections_step.query.context
367
+ memos = @extended.memos || EmptyObjects::EMPTY_HASH
368
+ while ext = @field_definition.extensions[@finish_extension_idx]
369
+ # These two are hardcoded here because of how they need to interact with runtime metadata.
370
+ # It would probably be better
371
+ case ext
372
+ when Schema::Field::ConnectionExtension
373
+ conns = ctx.schema.connections
374
+ @field_results.map!.each_with_index do |value, idx|
375
+ object = @extended.object[idx]
376
+ conn = conns.populate_connection(@field_definition, object, value, @arguments, ctx)
377
+ if conn
378
+ conn.was_authorized_by_scope_items = @was_scoped
379
+ end
380
+ conn
381
+ rescue GraphQL::RuntimeError => err
382
+ err
383
+ end
384
+ when Schema::Field::ScopeExtension
385
+ if @was_scoped.nil?
386
+ if (rt = @field_definition.type.unwrap).respond_to?(:scope_items)
387
+ @was_scoped = true
388
+ @field_results.map! { |v| v.nil? ? v : rt.scope_items(v, ctx) }
389
+ else
390
+ @was_scoped = false
391
+ end
392
+ end
393
+ else
394
+ memo = memos[@finish_extension_idx]
395
+ @field_results = ext.after_resolve(objects: @extended.object, arguments: @extended.arguments, context: ctx, values: @field_results, memo: memo) # rubocop:disable Development/ContextIsPassedCop
396
+ end
397
+ @finish_extension_idx += 1
398
+ if any_lazy_results?
399
+ @runner.dataloader.lazy_at_depth(path.size, self)
400
+ return
401
+ end
402
+ end
403
+
404
+ @finish_extension_idx = nil
405
+ build_results
406
+ end
407
+
408
+ def build_results
409
+ return_type = @field_definition.type
410
+ return_result_type = return_type.unwrap
411
+
412
+ @post_processors&.each do |post_processor|
413
+ @field_results = post_processor.after_resolve(@field_results)
414
+ end
415
+
416
+ if return_result_type.kind.composite?
417
+ @static_type = return_result_type
418
+ if @ast_nodes
419
+ @next_selections = []
420
+ @ast_nodes.each do |ast_node|
421
+ @next_selections.concat(ast_node.selections)
422
+ end
423
+ else
424
+ @next_selections = @ast_node.selections
425
+ end
426
+
427
+ @all_next_objects = []
428
+ @all_next_results = []
429
+
430
+ is_list = return_type.list?
431
+ is_non_null = return_type.non_null?
432
+ i = 0
433
+ s = @results.size
434
+ while i < s do
435
+ result_h = @results[i]
436
+ result = @field_results[i]
437
+ i += 1
438
+ build_graphql_result(result_h, @key, result, return_type, is_non_null, is_list, false)
439
+ end
440
+ @enqueued_authorization = true
441
+
442
+ if @pending_steps.nil? || @pending_steps.size == 0
443
+ enqueue_next_steps
444
+ else
445
+ # Do nothing -- it will enqueue itself later
446
+ end
447
+ else
448
+ ctx = @selections_step.query.context
449
+ i = 0
450
+ s = @results.size
451
+ while i < s do
452
+ result_h = @results[i]
453
+ field_result = @field_results[i]
454
+ i += 1
455
+ finish_leaf_result(result_h, @key, field_result, return_type, ctx)
456
+ end
457
+ end
458
+ end
459
+
460
+ 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)
462
+
463
+ @directive_finalizers&.each { |f| @runner.add_finalizer(ctx.query, result_h, key, f) }
464
+ result_h[@key] = final_field_result
465
+ end
466
+
467
+ def build_leaf_result(field_result, return_type, ctx, is_from_array)
468
+ if field_result.nil?
469
+ if return_type.non_null?
470
+ add_non_null_error(is_from_array)
471
+ else
472
+ nil
473
+ end
474
+ elsif field_result.is_a?(Finalizer)
475
+ if field_result.is_a?(GraphQL::RuntimeError)
476
+ add_graphql_error(field_result)
477
+ else
478
+ field_result.path = path
479
+ @runner.add_finalizer(ctx.query, result_h, key, field_result)
480
+ end
481
+ elsif return_type.list?
482
+ if return_type.non_null?
483
+ return_type = return_type.of_type
484
+ end
485
+
486
+ inner_type = return_type.of_type
487
+ field_result.map { |item| build_leaf_result(item, inner_type, ctx, true) }
488
+ else
489
+ return_type.coerce_result(field_result, ctx)
490
+ end
491
+ end
492
+
493
+ def enqueue_next_steps
494
+ if !@all_next_results.empty?
495
+ @all_next_objects.compact!
496
+
497
+ query = @selections_step.query
498
+ ctx = query.context
499
+ if @static_type.kind.abstract?
500
+ next_objects_by_type = Hash.new { |h, obj_t| h[obj_t] = [] }.compare_by_identity
501
+ next_results_by_type = Hash.new { |h, obj_t| h[obj_t] = [] }.compare_by_identity
502
+
503
+ @all_next_objects.each_with_index do |next_object, i|
504
+ result = @all_next_results[i]
505
+ if (object_type = @runner.runtime_type_at[result])
506
+ # OK
507
+ else
508
+ query.current_trace.begin_resolve_type(@static_type, next_object, query.context)
509
+ object_type = ResolveTypeStep.resolve_type(@static_type, next_object, query)
510
+ if object_type.is_a?(Array)
511
+ object_type, next_object = object_type
512
+ end
513
+ if @runner.resolves_lazies && @runner.lazy?(object_type)
514
+ # TODO batch this
515
+ object_type, next_object = sync(object_type)
516
+ end
517
+ ResolveTypeStep.assert_valid_resolved_type(@static_type, object_type, next_object, self)
518
+ query.current_trace.end_resolve_type(@static_type, next_object, query.context, object_type)
519
+ @runner.runtime_type_at[result] = object_type
520
+ end
521
+ next_objects_by_type[object_type] << next_object
522
+ next_results_by_type[object_type] << result
523
+ end
524
+
525
+ next_objects_by_type.each do |obj_type, next_objects|
526
+ query.current_trace.objects(obj_type, next_objects, ctx)
527
+ @runner.add_step(SelectionsStep.new(
528
+ path: path,
529
+ parent_type: obj_type,
530
+ selections: @next_selections,
531
+ objects: next_objects,
532
+ results: next_results_by_type[obj_type],
533
+ runner: @runner,
534
+ query: query,
535
+ ))
536
+ end
537
+ else
538
+ query.current_trace.objects(@static_type, @all_next_objects, ctx)
539
+ @runner.add_step(SelectionsStep.new(
540
+ path: path,
541
+ parent_type: @static_type,
542
+ selections: @next_selections,
543
+ objects: @all_next_objects,
544
+ results: @all_next_results,
545
+ runner: @runner,
546
+ query: query,
547
+ ))
548
+ end
549
+ end
550
+ end
551
+
552
+ def authorized_finished(step)
553
+ @pending_steps.delete(step)
554
+ if @enqueued_authorization && @pending_steps.size == 0
555
+ @runner.add_step(self)
556
+ end
557
+ end
558
+
559
+ def add_non_null_error(is_from_array)
560
+ 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)
562
+ end
563
+
564
+ def set_current_field(new_value = @field_definition)
565
+ Fiber[:__graphql_current_field] = new_value
566
+ end
567
+
568
+ private
569
+
570
+ def build_graphql_result(graphql_result, key, field_result, return_type, is_nn, is_list, is_from_array) # rubocop:disable Metrics/ParameterLists
571
+ if field_result.nil?
572
+ if is_nn
573
+ graphql_result[key] = add_non_null_error(is_from_array)
574
+ else
575
+ graphql_result[key] = nil
576
+ end
577
+ elsif field_result.is_a?(Finalizer)
578
+ graphql_result[key] = if field_result.is_a?(GraphQL::RuntimeError)
579
+ add_graphql_error(field_result)
580
+ else
581
+ field_result.path = path
582
+ @runner.add_finalizer(@selections_step.query, graphql_result, key, field_result)
583
+ field_result
584
+ end
585
+ elsif is_list
586
+ if is_nn
587
+ return_type = return_type.of_type
588
+ end
589
+ inner_type = return_type.of_type
590
+ inner_type_nn = inner_type.non_null?
591
+ inner_type_l = inner_type.list?
592
+ list_result = graphql_result[key] = []
593
+ @directive_finalizers&.each { |f| @runner.add_finalizer(@selections_step.query, list_result, nil, f) }
594
+ i = 0
595
+ s = field_result.size
596
+ while i < s
597
+ inner_f_r = field_result[i]
598
+ build_graphql_result(list_result, i, inner_f_r, inner_type, inner_type_nn, inner_type_l, true)
599
+ i += 1
600
+ end
601
+ elsif @runner.resolves_lazies || (
602
+ @static_type.kind.object? ?
603
+ @runner.authorizes?(@static_type, @selections_step.query.context) :
604
+ (
605
+ (runtime_type, _ignored_new_value = ResolveTypeStep.resolve_type(@static_type, field_result, @selections_step.query)) &&
606
+ (@runner.runtime_type_at[graphql_result] = runtime_type) &&
607
+ @runner.authorizes?(runtime_type, @selections_step.query.context)
608
+ ))
609
+ obj_step = PrepareObjectStep.new(
610
+ object: field_result,
611
+ runner: @runner,
612
+ field_resolve_step: self,
613
+ graphql_result: graphql_result,
614
+ next_objects: @all_next_objects,
615
+ next_results: @all_next_results,
616
+ is_non_null: is_nn,
617
+ key: key,
618
+ is_from_array: is_from_array,
619
+ )
620
+ ps = @pending_steps ||= []
621
+ ps << obj_step
622
+ @runner.add_step(obj_step)
623
+ else
624
+ next_result_h = {}.compare_by_identity
625
+ @all_next_results << next_result_h
626
+ @directive_finalizers&.each { |f| @runner.add_finalizer(@selections_step.query, next_result_h, nil, f) }
627
+ @all_next_objects << field_result
628
+ @runner.static_type_at[next_result_h] = @static_type
629
+ graphql_result[key] = next_result_h
630
+ end
631
+ end
632
+
633
+ def resolve_batch(objects, context, args_hash)
634
+ dyn_ins = @field_definition.dynamic_introspection
635
+ method_receiver = dyn_ins ? @field_definition.owner : @parent_type
636
+ case @field_definition.execution_mode
637
+ when :resolve_batch
638
+ begin
639
+ method_receiver.public_send(@field_definition.execution_mode_key, objects, context, **args_hash)
640
+ rescue GraphQL::ExecutionError => exec_err
641
+ error_instance_array(objects.size, exec_err)
642
+ rescue StandardError => stderr
643
+ begin
644
+ context.query.handle_or_reraise(stderr, field: @field_definition, arguments: @arguments, object: nil)
645
+ rescue GraphQL::ExecutionError => exec_err
646
+ error_instance_array(objects.size, exec_err)
647
+ end
648
+ end
649
+ when :resolve_static
650
+ result = begin
651
+ method_receiver.public_send(@field_definition.execution_mode_key, context, **args_hash)
652
+ rescue GraphQL::ExecutionError => err
653
+ err
654
+ rescue StandardError => stderr
655
+ begin
656
+ context.query.handle_or_reraise(stderr, field: @field_definition, arguments: @arguments, object: nil)
657
+ rescue GraphQL::ExecutionError => err
658
+ err
659
+ end
660
+ end
661
+ Array.new(objects.size, result)
662
+ when :resolve_each
663
+ objects.map do |o|
664
+ passed_in_obj = dyn_ins ? o.object : o
665
+ method_receiver.public_send(@field_definition.execution_mode_key, passed_in_obj, context, **args_hash)
666
+ rescue GraphQL::ExecutionError => err
667
+ err
668
+ rescue StandardError => stderr
669
+ begin
670
+ context.query.handle_or_reraise(stderr, field: @field_definition, arguments: @arguments, object: o)
671
+ rescue GraphQL::ExecutionError => err
672
+ err
673
+ end
674
+ end
675
+ when :hash_key
676
+ k = @field_definition.execution_mode_key
677
+ objects.map { |o| o[k] }
678
+ when :direct_send
679
+ m = @field_definition.execution_mode_key
680
+ objects.map do |o|
681
+ o.public_send(m, **args_hash)
682
+ rescue GraphQL::ExecutionError => err
683
+ err
684
+ rescue StandardError => stderr
685
+ begin
686
+ @selections_step.query.handle_or_reraise(stderr, object: o, field: @field_definition, arguments: args_hash)
687
+ rescue GraphQL::ExecutionError => ex_err
688
+ ex_err
689
+ end
690
+ end
691
+ when :dig
692
+ objects.map { |o| o.dig(*@field_definition.execution_mode_key) }
693
+ when :dataload
694
+ if (k = @field_definition.execution_mode_key).is_a?(Class)
695
+ context.dataload_all(k, objects)
696
+ elsif (source_class = k[:with])
697
+ if (batch_args = k[:by])
698
+ context.dataload_all(source_class, *batch_args, objects)
699
+ else
700
+ context.dataload_all(source_class, objects)
701
+ end
702
+ elsif (model = k[:model])
703
+ value_method = k[:using]
704
+ values = objects.map(&value_method)
705
+ context.dataload_all_records(model, values, find_by: k[:find_by])
706
+ elsif (assoc = k[:association])
707
+ if assoc == true
708
+ assoc = @field_definition.original_name
709
+ end
710
+ context.dataload_all_associations(objects, assoc, scope: k[:scope])
711
+ else
712
+ raise ArgumentError, "Unexpected `dataload: ...` configuration: #{k.inspect}"
713
+ end
714
+ when :resolver_class
715
+ results = Array.new(objects.size, nil)
716
+ ps = @pending_steps ||= []
717
+ objects.each_with_index do |o, idx|
718
+ resolver_inst = @field_definition.resolver.new(object: o, context: context, field: @field_definition)
719
+ ps << resolver_inst
720
+ resolver_inst.field_resolve_step = self
721
+ resolver_inst.prepared_arguments = args_hash
722
+ resolver_inst.exec_result = results
723
+ resolver_inst.exec_index = idx
724
+ @runner.add_step(resolver_inst)
725
+ resolver_inst
726
+ end
727
+ results
728
+ when :resolve_legacy_instance_method
729
+ @selections_step.graphql_objects.map do |obj_inst|
730
+ obj_inst.public_send(@field_definition.execution_mode_key, **args_hash)
731
+ rescue GraphQL::ExecutionError => exec_err
732
+ exec_err
733
+ end
734
+ else
735
+ raise "Batching execution for #{path} not implemented (execution_mode: #{@execution_mode.inspect}); provide `resolve_static:`, `resolve_batch:`, `hash_key:`, `method:`, or use a compatibility plug-in"
736
+ end
737
+ end
738
+
739
+ def error_instance_array(size, err_prototype)
740
+ Array.new(size) { err_prototype.dup }
741
+ end
742
+ end
743
+ end
744
+ end