graphql 2.5.23 → 2.6.1

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