graphql 2.5.20 → 2.5.21

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 (34) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +3 -3
  3. data/lib/graphql/execution/multiplex.rb +1 -1
  4. data/lib/graphql/execution/next/field_resolve_step.rb +690 -0
  5. data/lib/graphql/execution/next/load_argument_step.rb +60 -0
  6. data/lib/graphql/execution/{batching → next}/prepare_object_step.rb +26 -9
  7. data/lib/graphql/execution/{batching → next}/runner.rb +65 -28
  8. data/lib/graphql/execution/{batching → next}/selections_step.rb +1 -1
  9. data/lib/graphql/execution/{batching.rb → next.rb} +19 -12
  10. data/lib/graphql/execution.rb +1 -0
  11. data/lib/graphql/introspection/dynamic_fields.rb +5 -1
  12. data/lib/graphql/introspection/entry_points.rb +5 -1
  13. data/lib/graphql/pagination/connection.rb +2 -0
  14. data/lib/graphql/pagination/connections.rb +32 -0
  15. data/lib/graphql/schema/argument.rb +1 -0
  16. data/lib/graphql/schema/build_from_definition.rb +12 -25
  17. data/lib/graphql/schema/field/connection_extension.rb +15 -35
  18. data/lib/graphql/schema/field/scope_extension.rb +22 -13
  19. data/lib/graphql/schema/field.rb +52 -58
  20. data/lib/graphql/schema/field_extension.rb +33 -0
  21. data/lib/graphql/schema/member/base_dsl_methods.rb +1 -1
  22. data/lib/graphql/schema/member/has_authorization.rb +35 -0
  23. data/lib/graphql/schema/member/has_dataloader.rb +8 -0
  24. data/lib/graphql/schema/member/has_fields.rb +5 -4
  25. data/lib/graphql/schema/member.rb +5 -0
  26. data/lib/graphql/schema/object.rb +1 -0
  27. data/lib/graphql/schema/resolver.rb +42 -0
  28. data/lib/graphql/types/relay/has_node_field.rb +10 -2
  29. data/lib/graphql/types/relay/has_nodes_field.rb +10 -2
  30. data/lib/graphql/types/relay/node_behaviors.rb +13 -2
  31. data/lib/graphql/version.rb +1 -1
  32. metadata +9 -8
  33. data/lib/graphql/execution/batching/field_compatibility.rb +0 -150
  34. data/lib/graphql/execution/batching/field_resolve_step.rb +0 -408
@@ -0,0 +1,690 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module Execution
4
+ module Next
5
+ class FieldResolveStep
6
+ def initialize(parent_type:, runner:, key:, selections_step:)
7
+ @selections_step = selections_step
8
+ @key = key
9
+ @parent_type = parent_type
10
+ @ast_node = @ast_nodes = nil
11
+ @runner = runner
12
+ @field_definition = nil
13
+ @arguments = nil
14
+ @field_results = nil
15
+ @path = nil
16
+ @enqueued_authorization = false
17
+ @all_next_objects = nil
18
+ @all_next_results = nil
19
+ @static_type = nil
20
+ @next_selections = nil
21
+ @object_is_authorized = nil
22
+ @finish_extension_idx = nil
23
+ @was_scoped = nil
24
+ @pending_steps = 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
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 coerce_arguments(argument_owner, ast_arguments_or_hash)
52
+ arg_defns = argument_owner.arguments(@selections_step.query.context)
53
+ if arg_defns.empty?
54
+ return EmptyObjects::EMPTY_HASH
55
+ end
56
+ args_hash = {}
57
+ if ast_arguments_or_hash.is_a?(Hash)
58
+ ast_arguments_or_hash.each do |key, value|
59
+ key_s = nil
60
+ arg_defn = arg_defns.each_value.find { |a|
61
+ a.keyword == key || a.graphql_name == (key_s ||= String(key))
62
+ }
63
+ coerce_argument_value(args_hash, arg_defn, value)
64
+ end
65
+ else
66
+ ast_arguments_or_hash.each { |arg_node|
67
+ arg_defn = arg_defns[arg_node.name]
68
+ coerce_argument_value(args_hash, arg_defn, arg_node.value)
69
+ }
70
+ end
71
+ # TODO refactor the loop above into this one
72
+ arg_defns.each do |arg_graphql_name, arg_defn|
73
+ if arg_defn.default_value? && !args_hash.key?(arg_defn.keyword)
74
+ coerce_argument_value(args_hash, arg_defn, arg_defn.default_value)
75
+ end
76
+ end
77
+
78
+ args_hash
79
+ end
80
+
81
+ def coerce_argument_value(arguments, arg_defn, arg_value, target_keyword: arg_defn.keyword, as_type: nil)
82
+ arg_t = as_type || arg_defn.type
83
+ if arg_t.non_null?
84
+ arg_t = arg_t.of_type
85
+ end
86
+
87
+ arg_value = if arg_value.is_a?(Language::Nodes::VariableIdentifier)
88
+ vars = @selections_step.query.variables
89
+ if vars.key?(arg_value.name)
90
+ vars[arg_value.name]
91
+ elsif vars.key?(arg_value.name.to_sym)
92
+ vars[arg_value.name.to_sym]
93
+ else
94
+ return # not present
95
+ end
96
+ elsif arg_value.is_a?(Language::Nodes::NullValue)
97
+ nil
98
+ elsif arg_value.is_a?(Language::Nodes::Enum)
99
+ arg_value.name
100
+ elsif arg_value.is_a?(Language::Nodes::InputObject)
101
+ arg_value.arguments # rubocop:disable Development/ContextIsPassedCop
102
+ else
103
+ arg_value
104
+ end
105
+
106
+ ctx = @selections_step.query.context
107
+ arg_value = if arg_t.list?
108
+ if arg_value.nil?
109
+ arg_value
110
+ else
111
+ arg_value = Array(arg_value)
112
+ inner_t = arg_t.of_type
113
+ result = Array.new(arg_value.size)
114
+ arg_value.each_with_index { |v, i| coerce_argument_value(result, arg_defn, v, target_keyword: i, as_type: inner_t) }
115
+ result
116
+ end
117
+ elsif arg_t.kind.leaf?
118
+ begin
119
+ arg_t.coerce_input(arg_value, ctx)
120
+ rescue GraphQL::UnauthorizedEnumValueError => enum_err
121
+ begin
122
+ @runner.schema.unauthorized_object(enum_err)
123
+ rescue GraphQL::ExecutionError => ex_err
124
+ ex_err
125
+ end
126
+ end
127
+ elsif arg_t.kind.input_object?
128
+ input_obj_args = coerce_arguments(arg_t, arg_value)
129
+ arg_t.new(nil, ruby_kwargs: input_obj_args, context: @selections_step.query.context, defaults_used: nil)
130
+ else
131
+ raise "Unsupported argument value: #{arg_t.to_type_signature} / #{arg_value.class} (#{arg_value.inspect})"
132
+ end
133
+
134
+ if as_type.nil? # only on root arguments, not list elements
135
+ arg_value = begin
136
+ begin
137
+ arg_defn.prepare_value(nil, arg_value, context: ctx)
138
+ rescue StandardError => err
139
+ @runner.schema.handle_or_reraise(ctx, err)
140
+ end
141
+ rescue GraphQL::ExecutionError => exec_err
142
+ exec_err
143
+ end
144
+ end
145
+
146
+ if arg_value.is_a?(GraphQL::Error)
147
+ @arguments = arg_value
148
+ elsif arg_defn.loads && as_type.nil? && !arg_value.nil?
149
+ # This is for legacy compat:
150
+ load_receiver = if (r = @field_definition.resolver)
151
+ r.new(field: @field_definition, context: @selections_step.query.context, object: nil)
152
+ else
153
+ @field_definition
154
+ end
155
+ @pending_steps ||= []
156
+ if arg_t.list?
157
+ results = Array.new(arg_value.size, nil)
158
+ arguments[arg_defn.keyword] = results
159
+ arg_value.each_with_index do |inner_v, idx|
160
+ loads_step = LoadArgumentStep.new(
161
+ field_resolve_step: self,
162
+ load_receiver: load_receiver,
163
+ argument_value: inner_v,
164
+ argument_definition: arg_defn,
165
+ arguments: results,
166
+ argument_key: idx,
167
+ )
168
+ @pending_steps.push(loads_step)
169
+ @runner.add_step(loads_step)
170
+ end
171
+ else
172
+ loads_step = LoadArgumentStep.new(
173
+ field_resolve_step: self,
174
+ load_receiver: load_receiver,
175
+ argument_value: arg_value,
176
+ argument_definition: arg_defn,
177
+ arguments: arguments,
178
+ argument_key: arg_defn.keyword,
179
+ )
180
+ @pending_steps.push(loads_step)
181
+ @runner.add_step(loads_step)
182
+ end
183
+ else
184
+ arguments[target_keyword] = arg_value
185
+ end
186
+ nil
187
+ end
188
+
189
+ # Implement that Lazy API
190
+ def value
191
+ query = @selections_step.query
192
+ query.current_trace.begin_execute_field(@field_definition, @arguments, @field_results, query)
193
+ @field_results = sync(@field_results)
194
+ query.current_trace.end_execute_field(@field_definition, @arguments, @field_results, query, @field_results)
195
+ @runner.add_step(self)
196
+ true
197
+ end
198
+
199
+ def sync(lazy)
200
+ if lazy.is_a?(Array)
201
+ lazy.map! { |l| sync(l)}
202
+ else
203
+ @runner.schema.sync_lazy(lazy)
204
+ end
205
+ rescue GraphQL::UnauthorizedError => auth_err
206
+ @runner.schema.unauthorized_object(auth_err)
207
+ rescue GraphQL::ExecutionError => err
208
+ err
209
+ rescue StandardError => stderr
210
+ begin
211
+ @selections_step.query.handle_or_reraise(stderr)
212
+ rescue GraphQL::ExecutionError => ex_err
213
+ ex_err
214
+ end
215
+ end
216
+
217
+ def call
218
+ if @enqueued_authorization
219
+ enqueue_next_steps
220
+ elsif @finish_extension_idx
221
+ finish_extensions
222
+ elsif @field_results
223
+ build_results
224
+ elsif @arguments
225
+ execute_field
226
+ else
227
+ build_arguments
228
+ end
229
+ rescue StandardError => err
230
+ if @field_definition && !err.message.start_with?("Resolving ")
231
+ # TODO remove this check ^^^^^^ when NullDataloader isn't recursive
232
+ raise err, "Resolving #{@field_definition.path}: #{err.message}", err.backtrace
233
+ else
234
+ raise
235
+ end
236
+ end
237
+
238
+ def add_graphql_error(err)
239
+ err.path = path
240
+ err.ast_nodes = ast_nodes
241
+ @selections_step.query.context.add_error(err)
242
+ err
243
+ end
244
+
245
+ module AlwaysAuthorized
246
+ def self.[](_key)
247
+ true
248
+ end
249
+ end
250
+
251
+ def build_arguments
252
+ query = @selections_step.query
253
+ field_name = @ast_node.name
254
+ @field_definition = query.get_field(@parent_type, field_name) || raise("Invariant: no field found for #{@parent_type.to_type_signature}.#{ast_node.name}")
255
+ if field_name == "__typename"
256
+ # TODO handle custom introspection
257
+ @field_results = Array.new(@selections_step.objects.size, @parent_type.graphql_name)
258
+ @object_is_authorized = AlwaysAuthorized
259
+ build_results
260
+ return
261
+ end
262
+
263
+ arguments = coerce_arguments(@field_definition, @ast_node.arguments) # rubocop:disable Development/ContextIsPassedCop
264
+ @arguments ||= arguments # may have already been set to an error
265
+
266
+ if @pending_steps.nil? || @pending_steps.size == 0
267
+ execute_field
268
+ end
269
+ end
270
+
271
+ def execute_field
272
+ objects = @selections_step.objects
273
+ # TODO not as good because only one error?
274
+ if @arguments.is_a?(GraphQL::Error)
275
+ @field_results = Array.new(objects.size, @arguments)
276
+ @object_is_authorized = AlwaysAuthorized
277
+ build_results
278
+ return
279
+ end
280
+
281
+ query = @selections_step.query
282
+ ctx = query.context
283
+ if (v = @field_definition.validators).any? # rubocop:disable Development/NoneWithoutBlockCop
284
+ begin
285
+ Schema::Validator.validate!(v, nil, ctx, @arguments)
286
+ rescue GraphQL::RuntimeError => err
287
+ @field_results = Array.new(objects.size, err)
288
+ @object_is_authorized = AlwaysAuthorized
289
+ build_results
290
+ return
291
+ end
292
+ end
293
+
294
+ @field_definition.extras.each do |extra|
295
+ case extra
296
+ when :lookahead
297
+ if @arguments.frozen?
298
+ @arguments = @arguments.dup
299
+ end
300
+ @arguments[:lookahead] = Execution::Lookahead.new(
301
+ query: query,
302
+ ast_nodes: ast_nodes,
303
+ field: @field_definition,
304
+ )
305
+ when :ast_node
306
+ if @arguments.frozen?
307
+ @arguments = @arguments.dup
308
+ end
309
+ @arguments[:ast_node] = ast_node
310
+ else
311
+ raise ArgumentError, "This `extra` isn't supported yet: #{extra.inspect}. Open an issue on GraphQL-Ruby to add compatibility for it."
312
+ end
313
+ end
314
+
315
+ if @field_definition.dynamic_introspection
316
+ # TODO break this backwards compat somehow?
317
+ objects = @selections_step.graphql_objects
318
+ end
319
+
320
+ if @runner.authorization && @runner.authorizes?(@field_definition, ctx)
321
+ authorized_objects = []
322
+ @object_is_authorized = objects.map { |o|
323
+ is_authed = @field_definition.authorized?(o, @arguments, ctx)
324
+ if is_authed
325
+ authorized_objects << o
326
+ end
327
+ is_authed
328
+ }
329
+ if authorized_objects.size == 0
330
+ return
331
+ end
332
+ else
333
+ authorized_objects = objects
334
+ @object_is_authorized = AlwaysAuthorized
335
+ end
336
+
337
+ if @parent_type.default_relay? && authorized_objects.all? { |o| o.respond_to?(:was_authorized_by_scope_items?) && o.was_authorized_by_scope_items? }
338
+ @was_scoped = true
339
+ end
340
+
341
+ query.current_trace.begin_execute_field(@field_definition, @arguments, authorized_objects, query)
342
+ has_extensions = @field_definition.extensions.size > 0
343
+ if has_extensions
344
+ @extended = GraphQL::Schema::Field::ExtendedState.new(@arguments, authorized_objects)
345
+ @field_results = @field_definition.run_next_extensions_before_resolve(authorized_objects, @arguments, ctx, @extended) do |objs, args|
346
+ if (added_extras = @extended.added_extras)
347
+ args = args.dup
348
+ added_extras.each { |e| args.delete(e) }
349
+ end
350
+ resolve_batch(objs, ctx, args)
351
+ end
352
+ @finish_extension_idx = 0
353
+ else
354
+ @field_results = resolve_batch(authorized_objects, ctx, @arguments)
355
+ end
356
+
357
+ query.current_trace.end_execute_field(@field_definition, @arguments, authorized_objects, query, @field_results)
358
+
359
+ if any_lazy_results?
360
+ @runner.dataloader.lazy_at_depth(path.size, self)
361
+ elsif has_extensions
362
+ finish_extensions
363
+ elsif @pending_steps.nil? || @pending_steps.empty?
364
+ build_results
365
+ end
366
+ end
367
+
368
+ def any_lazy_results?
369
+ lazies = false
370
+ if @runner.resolves_lazies # TODO extract this
371
+ @field_results.each do |field_result|
372
+ if @runner.lazy?(field_result)
373
+ lazies = true
374
+ break
375
+ elsif field_result.is_a?(Array)
376
+ field_result.each do |inner_fr|
377
+ if @runner.lazy?(inner_fr)
378
+ break lazies = true
379
+ end
380
+ end
381
+ if lazies
382
+ break
383
+ end
384
+ end
385
+ end
386
+ end
387
+ lazies
388
+ end
389
+
390
+ def finish_extensions
391
+ ctx = @selections_step.query.context
392
+ memos = @extended.memos || EmptyObjects::EMPTY_HASH
393
+ while ext = @field_definition.extensions[@finish_extension_idx]
394
+ # These two are hardcoded here because of how they need to interact with runtime metadata.
395
+ # It would probably be better
396
+ case ext
397
+ when Schema::Field::ConnectionExtension
398
+ conns = ctx.schema.connections
399
+ @field_results = @field_results.map.each_with_index do |value, idx|
400
+ object = @extended.object[idx]
401
+ conn = conns.populate_connection(@field_definition, object, value, @arguments, ctx)
402
+ if conn
403
+ conn.was_authorized_by_scope_items = @was_scoped
404
+ end
405
+ conn
406
+ end
407
+ when Schema::Field::ScopeExtension
408
+ if @was_scoped.nil?
409
+ if (rt = @field_definition.type.unwrap).respond_to?(:scope_items)
410
+ @was_scoped = true
411
+ @field_results = @field_results.map { |v| v.nil? ? v : rt.scope_items(v, ctx) }
412
+ else
413
+ @was_scoped = false
414
+ end
415
+ end
416
+ else
417
+ memo = memos[@finish_extension_idx]
418
+ @field_results = ext.after_resolve_next(objects: @extended.object, arguments: @extended.arguments, context: ctx, values: @field_results, memo: memo) # rubocop:disable Development/ContextIsPassedCop
419
+ end
420
+ @finish_extension_idx += 1
421
+ if any_lazy_results?
422
+ @runner.dataloader.lazy_at_depth(path.size, self)
423
+ return
424
+ end
425
+ end
426
+
427
+ @finish_extension_idx = nil
428
+ build_results
429
+ end
430
+
431
+ def build_results
432
+ return_type = @field_definition.type
433
+ return_result_type = return_type.unwrap
434
+
435
+ if return_result_type.kind.composite?
436
+ @static_type = return_result_type
437
+ if @ast_nodes
438
+ @next_selections = []
439
+ @ast_nodes.each do |ast_node|
440
+ @next_selections.concat(ast_node.selections)
441
+ end
442
+ else
443
+ @next_selections = @ast_node.selections
444
+ end
445
+
446
+ @all_next_objects = []
447
+ @all_next_results = []
448
+
449
+ is_list = return_type.list?
450
+ is_non_null = return_type.non_null?
451
+ results = @selections_step.results
452
+ field_result_idx = 0
453
+ i = 0
454
+ s = results.size
455
+ while i < s do
456
+ result_h = results[i]
457
+ if @object_is_authorized[i]
458
+ result = @field_results[field_result_idx]
459
+ field_result_idx += 1
460
+ else
461
+ result = nil
462
+ end
463
+ i += 1
464
+ build_graphql_result(result_h, @key, result, return_type, is_non_null, is_list, false)
465
+ end
466
+ @enqueued_authorization = true
467
+
468
+ if @pending_steps.nil? || @pending_steps.size == 0
469
+ enqueue_next_steps
470
+ else
471
+ # Do nothing -- it will enqueue itself later
472
+ end
473
+ else
474
+ ctx = @selections_step.query.context
475
+ results = @selections_step.results
476
+ field_result_idx = 0
477
+ i = 0
478
+ s = results.size
479
+ while i < s do
480
+ result_h = results[i]
481
+ if @object_is_authorized[i]
482
+ field_result = @field_results[field_result_idx]
483
+ field_result_idx += 1
484
+ else
485
+ field_result = nil
486
+ end
487
+ i += 1
488
+ result_h[@key] = if field_result.nil?
489
+ if return_type.non_null?
490
+ add_non_null_error(false)
491
+ else
492
+ nil
493
+ end
494
+ elsif field_result.is_a?(GraphQL::Error)
495
+ add_graphql_error(field_result)
496
+ else
497
+ # TODO `nil`s in [T!] types aren't handled
498
+ return_type.coerce_result(field_result, ctx)
499
+ end
500
+ end
501
+ end
502
+ end
503
+
504
+ def enqueue_next_steps
505
+ if !@all_next_results.empty?
506
+ @all_next_objects.compact!
507
+
508
+ if @static_type.kind.abstract?
509
+ next_objects_by_type = Hash.new { |h, obj_t| h[obj_t] = [] }.compare_by_identity
510
+ next_results_by_type = Hash.new { |h, obj_t| h[obj_t] = [] }.compare_by_identity
511
+
512
+ @all_next_objects.each_with_index do |next_object, i|
513
+ result = @all_next_results[i]
514
+ if (object_type = @runner.runtime_type_at[result])
515
+ # OK
516
+ else
517
+ object_type = @runner.resolve_type(@static_type, next_object, @selections_step.query)
518
+ @runner.runtime_type_at[result] = object_type
519
+ end
520
+ next_objects_by_type[object_type] << next_object
521
+ next_results_by_type[object_type] << result
522
+ end
523
+
524
+ next_objects_by_type.each do |obj_type, next_objects|
525
+ @runner.add_step(SelectionsStep.new(
526
+ path: path,
527
+ parent_type: obj_type,
528
+ selections: @next_selections,
529
+ objects: next_objects,
530
+ results: next_results_by_type[obj_type],
531
+ runner: @runner,
532
+ query: @selections_step.query,
533
+ ))
534
+ end
535
+ else
536
+ @runner.add_step(SelectionsStep.new(
537
+ path: path,
538
+ parent_type: @static_type,
539
+ selections: @next_selections,
540
+ objects: @all_next_objects,
541
+ results: @all_next_results,
542
+ runner: @runner,
543
+ query: @selections_step.query,
544
+ ))
545
+ end
546
+ end
547
+ end
548
+
549
+ def authorized_finished(step)
550
+ @pending_steps.delete(step)
551
+ if @enqueued_authorization && @pending_steps.size == 0
552
+ @runner.add_step(self)
553
+ end
554
+ end
555
+
556
+ def add_non_null_error(is_from_array)
557
+ err = InvalidNullError.new(@parent_type, @field_definition, ast_nodes, is_from_array: is_from_array, path: path)
558
+ @runner.schema.type_error(err, @selections_step.query.context)
559
+ end
560
+
561
+ private
562
+
563
+ def build_graphql_result(graphql_result, key, field_result, return_type, is_nn, is_list, is_from_array) # rubocop:disable Metrics/ParameterLists
564
+ if field_result.nil?
565
+ if is_nn
566
+ graphql_result[key] = add_non_null_error(is_from_array)
567
+ else
568
+ graphql_result[key] = nil
569
+ end
570
+ elsif field_result.is_a?(GraphQL::Error)
571
+ graphql_result[key] = add_graphql_error(field_result)
572
+ elsif is_list
573
+ if is_nn
574
+ return_type = return_type.of_type
575
+ end
576
+ inner_type = return_type.of_type
577
+ inner_type_nn = inner_type.non_null?
578
+ inner_type_l = inner_type.list?
579
+ list_result = graphql_result[key] = []
580
+ i = 0
581
+ s = field_result.size
582
+ while i < s
583
+ inner_f_r = field_result[i]
584
+ build_graphql_result(list_result, i, inner_f_r, inner_type, inner_type_nn, inner_type_l, true)
585
+ i += 1
586
+ end
587
+ elsif @runner.resolves_lazies || (@runner.authorization && (@static_type.kind.object? ? @runner.authorizes?(@static_type, @selections_step.query.context) : (
588
+ (runtime_type = (@runner.runtime_type_at[graphql_result] = @runner.resolve_type(@static_type, field_result, @selections_step.query))
589
+ ) && @runner.authorizes?(runtime_type, @selections_step.query.context)
590
+ )))
591
+ obj_step = PrepareObjectStep.new(
592
+ static_type: @static_type,
593
+ object: field_result,
594
+ runner: @runner,
595
+ field_resolve_step: self,
596
+ graphql_result: graphql_result,
597
+ next_objects: @all_next_objects,
598
+ next_results: @all_next_results,
599
+ is_non_null: is_nn,
600
+ key: key,
601
+ is_from_array: is_from_array,
602
+ )
603
+ ps = @pending_steps ||= []
604
+ ps << obj_step
605
+ @runner.add_step(obj_step)
606
+ else
607
+ next_result_h = {}
608
+ @all_next_results << next_result_h
609
+ @all_next_objects << field_result
610
+ @runner.static_type_at[next_result_h] = @static_type
611
+ graphql_result[key] = next_result_h
612
+ end
613
+ end
614
+
615
+ def resolve_batch(objects, context, args_hash)
616
+ method_receiver = @field_definition.dynamic_introspection ? @field_definition.owner : @parent_type
617
+ case @field_definition.execution_next_mode
618
+ when :resolve_batch
619
+ if args_hash.empty?
620
+ method_receiver.public_send(@field_definition.execution_next_mode_key, objects, context)
621
+ else
622
+ method_receiver.public_send(@field_definition.execution_next_mode_key, objects, context, **args_hash)
623
+ end
624
+ when :resolve_static
625
+ result = if args_hash.empty?
626
+ method_receiver.public_send(@field_definition.execution_next_mode_key, context)
627
+ else
628
+ method_receiver.public_send(@field_definition.execution_next_mode_key, context, **args_hash)
629
+ end
630
+ Array.new(objects.size, result)
631
+ when :resolve_each
632
+ if args_hash.empty?
633
+ objects.map { |o| method_receiver.public_send(@field_definition.execution_next_mode_key, o, context) }
634
+ else
635
+ objects.map { |o| method_receiver.public_send(@field_definition.execution_next_mode_key, o, context, **args_hash) }
636
+ end
637
+ when :hash_key
638
+ objects.map { |o| o[@field_definition.execution_next_mode_key] }
639
+ when :direct_send
640
+ if args_hash.empty?
641
+ objects.map { |o| o.public_send(@field_definition.execution_next_mode_key) }
642
+ else
643
+ objects.map { |o| o.public_send(@field_definition.execution_next_mode_key, **args_hash) }
644
+ end
645
+ when :dig
646
+ objects.map { |o| o.dig(*@field_definition.execution_next_mode_key) }
647
+ when :resolver_class
648
+ results = Array.new(objects.size, nil)
649
+ ps = @pending_steps ||= []
650
+ objects.each_with_index do |o, idx|
651
+ resolver_inst = @field_definition.resolver.new(object: o, context: context, field: @field_definition)
652
+ ps << resolver_inst
653
+ resolver_inst.field_resolve_step = self
654
+ resolver_inst.prepared_arguments = args_hash
655
+ resolver_inst.exec_result = results
656
+ resolver_inst.exec_index = idx
657
+ @runner.add_step(resolver_inst)
658
+ resolver_inst
659
+ end
660
+ results
661
+ when :resolve_legacy_instance_method
662
+ @selections_step.graphql_objects.map do |obj_inst|
663
+ if @field_definition.dynamic_introspection
664
+ obj_inst = @owner.wrap(obj_inst, context)
665
+ end
666
+ if args_hash.empty?
667
+ obj_inst.public_send(@field_definition.execution_next_mode_key)
668
+ else
669
+ obj_inst.public_send(@field_definition.execution_next_mode_key, **args_hash)
670
+ end
671
+ end
672
+ else
673
+ raise "Batching execution for #{path} not implemented (execution_next_mode: #{@execution_next_mode.inspect}); provide `resolve_static:`, `resolve_batch:`, `hash_key:`, `method:`, or use a compatibility plug-in"
674
+ end
675
+ end
676
+
677
+ end
678
+
679
+ class RawValueFieldResolveStep < FieldResolveStep
680
+ def build_graphql_result(graphql_result, key, field_result, return_type, is_nn, is_list, is_from_array) # rubocop:disable Metrics/ParameterLists
681
+ if field_result.is_a?(Interpreter::RawValue)
682
+ graphql_result[key] = field_result.resolve
683
+ else
684
+ super
685
+ end
686
+ end
687
+ end
688
+ end
689
+ end
690
+ end