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