graphql 2.6.1 → 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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/analysis/query_complexity.rb +29 -13
  3. data/lib/graphql/backtrace/table.rb +10 -1
  4. data/lib/graphql/current.rb +7 -1
  5. data/lib/graphql/dataloader.rb +1 -1
  6. data/lib/graphql/execution/directive_checks.rb +2 -0
  7. data/lib/graphql/execution/field_resolve_step.rb +178 -65
  8. data/lib/graphql/execution/finalize.rb +21 -8
  9. data/lib/graphql/execution/input_values.rb +110 -38
  10. data/lib/graphql/execution/interpreter/arguments_cache.rb +3 -0
  11. data/lib/graphql/execution/interpreter/runtime.rb +36 -15
  12. data/lib/graphql/execution/load_argument_step.rb +41 -3
  13. data/lib/graphql/execution/next.rb +20 -12
  14. data/lib/graphql/execution/prepare_object_step.rb +24 -5
  15. data/lib/graphql/execution/resolve_type_step.rb +27 -0
  16. data/lib/graphql/execution/runner.rb +65 -30
  17. data/lib/graphql/execution/selections_step.rb +1 -1
  18. data/lib/graphql/execution.rb +8 -1
  19. data/lib/graphql/execution_error.rb +6 -12
  20. data/lib/graphql/introspection/entry_points.rb +2 -2
  21. data/lib/graphql/introspection/schema_type.rb +6 -2
  22. data/lib/graphql/language/lexer.rb +1 -1
  23. data/lib/graphql/language/parser.rb +1 -1
  24. data/lib/graphql/language.rb +8 -2
  25. data/lib/graphql/pagination/connections.rb +1 -3
  26. data/lib/graphql/query.rb +2 -2
  27. data/lib/graphql/schema/argument.rb +3 -3
  28. data/lib/graphql/schema/directive/feature.rb +4 -0
  29. data/lib/graphql/schema/directive/transform.rb +20 -0
  30. data/lib/graphql/schema/has_single_input_argument.rb +24 -13
  31. data/lib/graphql/schema/input_object.rb +4 -0
  32. data/lib/graphql/schema/interface.rb +1 -1
  33. data/lib/graphql/schema/introspection_system.rb +6 -21
  34. data/lib/graphql/schema/printer.rb +1 -1
  35. data/lib/graphql/schema/ractor_shareable.rb +1 -0
  36. data/lib/graphql/schema/relay_classic_mutation.rb +16 -2
  37. data/lib/graphql/schema/resolver.rb +0 -7
  38. data/lib/graphql/schema/subscription.rb +53 -8
  39. data/lib/graphql/schema/timeout.rb +2 -2
  40. data/lib/graphql/schema/validator/allow_blank_validator.rb +3 -3
  41. data/lib/graphql/schema/validator/allow_null_validator.rb +3 -3
  42. data/lib/graphql/schema/validator/exclusion_validator.rb +2 -2
  43. data/lib/graphql/schema/validator/format_validator.rb +3 -3
  44. data/lib/graphql/schema/validator/inclusion_validator.rb +2 -2
  45. data/lib/graphql/schema/validator/length_validator.rb +6 -6
  46. data/lib/graphql/schema/validator/numericality_validator.rb +19 -19
  47. data/lib/graphql/schema/validator/required_validator.rb +6 -4
  48. data/lib/graphql/schema/validator.rb +9 -0
  49. data/lib/graphql/schema/visibility/profile.rb +6 -4
  50. data/lib/graphql/schema/visibility/visit.rb +1 -1
  51. data/lib/graphql/schema/visibility.rb +30 -22
  52. data/lib/graphql/schema.rb +31 -10
  53. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +6 -0
  54. data/lib/graphql/subscriptions/event.rb +0 -1
  55. data/lib/graphql/tracing/perfetto_trace.rb +5 -3
  56. data/lib/graphql/version.rb +1 -1
  57. metadata +3 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8f525f949845c463742ad345a16e2441802ef69d89aca935fbe78dcfea419341
4
- data.tar.gz: 1175b9e2ddc5cf363833b2b9ab5be79e0837d445db5e22ceb5115708c139001e
3
+ metadata.gz: 16abc9c2a5eda0da251dbe7e14433241bc7d038d2527926832ceb29336fb857a
4
+ data.tar.gz: c89bd2a4b340ca30bfa7b823f51e4671972355d54bd56fdaed598c13a415c3b3
5
5
  SHA512:
6
- metadata.gz: bca56323051f3df8cc6d0ce3d91ee0ba0c125cda0b2e2ddf5deab0d18131cdca646aa7a8b3a0dfee98abd5ea6be25eee358ecd3c08ab885c31c946af8033ec05
7
- data.tar.gz: d4e993d8b2a3492113bb00175ddaed842ee6d10ca948d7c53ed6550007f7bd24c784214557bf4c33a35139a73909575bc9b93a2bff41d66dfdeeb235e8b4d00f
6
+ metadata.gz: 6452d2a517c502b4a8934582d060885a5f6462de244a1f331433f1076dc57a879cdd081e147f70925064a99607e0794fcf6eadad7a3f4a9111a5ab979c552554
7
+ data.tar.gz: b6a0219606d905fc885ab2192596d1acb1d92bce407defc751bfafb1a7199e9c32275c1d3202f3246251e72b80554698b1f54920f76178f1ddc81b41d628bacd
@@ -9,6 +9,8 @@ module GraphQL
9
9
  super
10
10
  @skip_introspection_fields = !query.schema.max_complexity_count_introspection_fields
11
11
  @complexities_on_type_by_query = {}
12
+ @intersect_cache = Hash.new { |h, k| h[k] = {}.compare_by_identity }.compare_by_identity
13
+ @possible_types_cache = {}.compare_by_identity
12
14
  end
13
15
 
14
16
  # Override this method to use the complexity result
@@ -159,8 +161,22 @@ module GraphQL
159
161
 
160
162
  def types_intersect?(query, a, b)
161
163
  return true if a == b
162
- a_types = query.types.possible_types(a)
163
- query.types.possible_types(b).any? { |t| a_types.include?(t) }
164
+
165
+ if a.object_id < b.object_id
166
+ first_cache = @intersect_cache[a]
167
+ second_key = b
168
+ else
169
+ first_cache = @intersect_cache[b]
170
+ second_key = a
171
+ end
172
+
173
+ if first_cache.key?(second_key)
174
+ first_cache[second_key]
175
+ else
176
+ a_types = @possible_types_cache[a] ||= query.types.possible_types(a).to_set
177
+ b_types = @possible_types_cache[b] ||= query.types.possible_types(b).to_set
178
+ first_cache[second_key] = a_types.intersect?(b_types)
179
+ end
164
180
  end
165
181
 
166
182
  # A hook which is called whenever a field's max complexity is calculated.
@@ -175,18 +191,16 @@ module GraphQL
175
191
  # @param inner_selections [Array<Hash<String, ScopedTypeComplexity>>] Field selections for a scope
176
192
  # @return [Integer] Total complexity value for all these selections in the parent scope
177
193
  def merged_max_complexity(query, inner_selections)
178
- # Aggregate a set of all unique field selection keys across all scopes.
179
- # Use a hash, but ignore the values; it's just a fast way to work with the keys.
180
- unique_field_keys = inner_selections.each_with_object({}) do |inner_selection, memo|
181
- memo.merge!(inner_selection)
194
+ child_scopes_by_key = {}
195
+ inner_selections.each do |inner_selection|
196
+ inner_selection.each do |k, v|
197
+ scopes = child_scopes_by_key[k] ||= []
198
+ scopes << v
199
+ end
182
200
  end
183
-
184
201
  # Add up the total cost for each unique field name's coalesced selections
185
- unique_field_keys.each_key.reduce(0) do |total, field_key|
186
- # Collect all child scopes for this field key;
187
- # all keys come with at least one scope.
188
- child_scopes = inner_selections.filter_map { _1[field_key] }
189
-
202
+ total = 0
203
+ child_scopes_by_key.each do |field_key, child_scopes|
190
204
  # Compute maximum possible cost of child selections;
191
205
  # composites merge their maximums, while leaf scopes are always zero.
192
206
  # FieldsWillMerge validation assures all scopes are uniformly composite or leaf.
@@ -214,8 +228,10 @@ module GraphQL
214
228
  child_complexity: maximum_children_cost,
215
229
  )
216
230
 
217
- total + maximum_cost
231
+ total += maximum_cost
218
232
  end
233
+
234
+ total
219
235
  end
220
236
 
221
237
  def legacy_merged_max_complexity(query, inner_selections)
@@ -90,7 +90,16 @@ module GraphQL
90
90
 
91
91
  if ast_node
92
92
  field_defn = query.get_field(result.graphql_result_type, ast_node.name)
93
- args = query.arguments_for(ast_node, field_defn).to_h
93
+ args = begin
94
+ if (cached_args = query.arguments_cache.cached_arguments_for(ast_node, field_defn))
95
+ cached_args.to_h
96
+ else
97
+ EmptyObjects::EMPTY_HASH
98
+ end
99
+ rescue StandardError => err
100
+ "Failed to load arguments, #{err.class}: #{err.message}"
101
+ end
102
+
94
103
  field_path = field_defn.path
95
104
  if ast_node.alias
96
105
  field_path += " as #{ast_node.alias}"
@@ -41,7 +41,13 @@ module GraphQL
41
41
  # @see GraphQL::Field#path for a string identifying this field
42
42
  # @return [GraphQL::Field, nil] The currently-running field, if there is one.
43
43
  def self.field
44
- Fiber[:__graphql_runtime_info]&.values&.first&.current_field
44
+ if (interpreter_info = Fiber[:__graphql_runtime_info])
45
+ interpreter_info.values&.first&.current_field
46
+ elsif (field = Fiber[:__graphql_current_field])
47
+ field
48
+ else
49
+ nil
50
+ end
45
51
  end
46
52
 
47
53
  # @return [Class, nil] The currently-running {Dataloader::Source} class, if there is one.
@@ -108,7 +108,7 @@ module GraphQL
108
108
  # @param batch_parameters [Array<Object>]
109
109
  # @return [GraphQL::Dataloader::Source] An instance of {source_class}, initialized with `self, *batch_parameters`,
110
110
  # and cached for the lifetime of this {Multiplex}.
111
- if RUBY_VERSION < "3" || RUBY_ENGINE != "ruby" # truffle-ruby wasn't doing well with the implementation below
111
+ if (RUBY_ENGINE == "ruby" && RUBY_ENGINE < "3") || RUBY_ENGINE == "truffleruby" # truffle-ruby wasn't doing well with the implementation below
112
112
  def with(source_class, *batch_args)
113
113
  batch_key = source_class.batch_key_for(*batch_args)
114
114
  @source_cache[source_class][batch_key] ||= begin
@@ -18,11 +18,13 @@ module GraphQL
18
18
  case name
19
19
  when SKIP
20
20
  args = query.arguments_for(directive_ast_node, directive_defn)
21
+ next if args.is_a?(GraphQL::ExecutionError)
21
22
  if args[:if] == true
22
23
  return false
23
24
  end
24
25
  when INCLUDE
25
26
  args = query.arguments_for(directive_ast_node, directive_defn)
27
+ next if args.is_a?(GraphQL::ExecutionError)
26
28
  if args[:if] == false
27
29
  return false
28
30
  end
@@ -21,7 +21,7 @@ module GraphQL
21
21
  @finish_extension_idx = nil
22
22
  @was_scoped = nil
23
23
  @pending_steps = nil
24
- @post_processors = @directive_finalizers = nil
24
+ @arguments_without_loads = @post_processors = @directive_finalizers = nil
25
25
  end
26
26
 
27
27
  attr_reader :ast_node, :key, :parent_type, :selections_step, :runner,
@@ -50,11 +50,14 @@ module GraphQL
50
50
 
51
51
  def value
52
52
  query = @selections_step.query
53
+ set_current_field
53
54
  query.current_trace.begin_execute_field(@field_definition, @arguments, @field_results, query)
54
55
  sync(@field_results)
55
56
  query.current_trace.end_execute_field(@field_definition, @arguments, @field_results, query, @field_results)
56
57
  @runner.add_step(self)
57
58
  true
59
+ ensure
60
+ set_current_field(nil)
58
61
  end
59
62
 
60
63
  def sync(lazy)
@@ -69,13 +72,14 @@ module GraphQL
69
72
  err
70
73
  rescue StandardError => stderr
71
74
  begin
72
- @selections_step.query.handle_or_reraise(stderr)
75
+ @selections_step.query.handle_or_reraise(stderr, field: @field_definition, arguments: @arguments, object: nil)
73
76
  rescue GraphQL::ExecutionError => ex_err
74
77
  ex_err
75
78
  end
76
79
  end
77
80
 
78
81
  def call
82
+ set_current_field if @field_definition
79
83
  if @enqueued_authorization
80
84
  enqueue_next_steps
81
85
  elsif @finish_extension_idx
@@ -94,46 +98,73 @@ module GraphQL
94
98
  else
95
99
  raise
96
100
  end
101
+ ensure
102
+ set_current_field(nil)
97
103
  end
98
104
 
99
105
  def add_graphql_error(err)
100
106
  err.path = path
101
- err.ast_nodes = ast_nodes
107
+ if err.ast_node.nil?
108
+ err.ast_nodes = ast_nodes
109
+ end
102
110
  @selections_step.query.context.add_error(err)
103
111
  err
104
112
  end
105
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
+
106
126
  def build_arguments
107
127
  query = @selections_step.query
108
128
  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
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
112
136
 
113
137
  if (@pending_steps.nil? || @pending_steps.size == 0) &&
114
138
  @field_results.nil? # Make sure the arguments flow didn't already call through
115
139
  execute_field
116
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
117
151
  end
118
152
 
119
153
  def execute_field
120
154
  objects = @selections_step.objects
121
- @results = @selections_step.results
122
- # TODO not as good because only one error?
123
155
  if @arguments.is_a?(GraphQL::RuntimeError)
124
- @field_results = Array.new(objects.size, @arguments)
125
- build_results
156
+ build_errors_result(nil, @arguments)
126
157
  return
127
158
  end
128
159
 
160
+ @results = @selections_step.results
129
161
  query = @selections_step.query
130
162
  ctx = query.context
131
163
  if (v = @field_definition.validators).any? # rubocop:disable Development/NoneWithoutBlockCop
132
164
  begin
133
165
  Schema::Validator.validate!(v, nil, ctx, @arguments)
134
166
  rescue GraphQL::RuntimeError => err
135
- @field_results = Array.new(objects.size, err)
136
- build_results
167
+ build_errors_result(nil, err)
137
168
  return
138
169
  end
139
170
  end
@@ -160,25 +191,40 @@ module GraphQL
160
191
  end
161
192
 
162
193
  if @field_definition.dynamic_introspection
163
- # TODO break this backwards compat somehow?
164
- objects = @selections_step.graphql_objects
194
+ objects = @selections_step.graphql_objects.map { |o| @field_definition.owner.wrap(o, ctx) }
165
195
  end
166
196
 
167
- if @runner.authorization && @runner.authorizes?(@field_definition, ctx)
197
+ if @runner.authorizes?(@field_definition, ctx)
168
198
  authorized_objects = []
169
199
  authorized_results = []
170
200
  l = objects.size
171
201
  i = 0
172
202
  while i < l
173
203
  o = objects[i]
174
- if @field_definition.authorized?(o, @arguments, ctx)
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
175
218
  authorized_results << @results[i]
176
219
  authorized_objects << o
177
220
  else
178
221
  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]
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
182
228
  rescue GraphQL::ExecutionError => exec_err
183
229
  add_graphql_error(exec_err)
184
230
  end
@@ -220,27 +266,37 @@ module GraphQL
220
266
  if directives
221
267
  directives.each do |dir_node|
222
268
  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
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
244
300
  end
245
301
  end
246
302
  end
@@ -267,10 +323,20 @@ module GraphQL
267
323
 
268
324
  if any_lazy_results?
269
325
  @runner.dataloader.lazy_at_depth(path.size, self)
270
- elsif has_extensions
271
- finish_extensions
272
326
  elsif @pending_steps.nil? || @pending_steps.empty?
273
- build_results
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)
274
340
  end
275
341
  end
276
342
 
@@ -312,6 +378,8 @@ module GraphQL
312
378
  conn.was_authorized_by_scope_items = @was_scoped
313
379
  end
314
380
  conn
381
+ rescue GraphQL::RuntimeError => err
382
+ err
315
383
  end
316
384
  when Schema::Field::ScopeExtension
317
385
  if @was_scoped.nil?
@@ -390,9 +458,16 @@ module GraphQL
390
458
  end
391
459
 
392
460
  def finish_leaf_result(result_h, key, field_result, return_type, ctx)
393
- final_field_result = if field_result.nil?
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?
394
469
  if return_type.non_null?
395
- add_non_null_error(false)
470
+ add_non_null_error(is_from_array)
396
471
  else
397
472
  nil
398
473
  end
@@ -403,13 +478,16 @@ module GraphQL
403
478
  field_result.path = path
404
479
  @runner.add_finalizer(ctx.query, result_h, key, field_result)
405
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) }
406
488
  else
407
- # TODO `nil`s in [T!] types aren't handled
408
489
  return_type.coerce_result(field_result, ctx)
409
490
  end
410
-
411
- @directive_finalizers&.each { |f| @runner.add_finalizer(ctx.query, result_h, key, f) }
412
- result_h[@key] = final_field_result
413
491
  end
414
492
 
415
493
  def enqueue_next_steps
@@ -427,7 +505,17 @@ module GraphQL
427
505
  if (object_type = @runner.runtime_type_at[result])
428
506
  # OK
429
507
  else
430
- object_type = @runner.resolve_type(@static_type, next_object, query)
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)
431
519
  @runner.runtime_type_at[result] = object_type
432
520
  end
433
521
  next_objects_by_type[object_type] << next_object
@@ -469,10 +557,14 @@ module GraphQL
469
557
  end
470
558
 
471
559
  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)
560
+ err = @parent_type::InvalidNullError.new(@parent_type, @field_definition, ast_nodes, is_from_array: is_from_array, path: path)
473
561
  @runner.schema.type_error(err, @selections_step.query.context)
474
562
  end
475
563
 
564
+ def set_current_field(new_value = @field_definition)
565
+ Fiber[:__graphql_current_field] = new_value
566
+ end
567
+
476
568
  private
477
569
 
478
570
  def build_graphql_result(graphql_result, key, field_result, return_type, is_nn, is_list, is_from_array) # rubocop:disable Metrics/ParameterLists
@@ -507,13 +599,13 @@ module GraphQL
507
599
  i += 1
508
600
  end
509
601
  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
- )))
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
+ ))
517
609
  obj_step = PrepareObjectStep.new(
518
610
  object: field_result,
519
611
  runner: @runner,
@@ -539,26 +631,46 @@ module GraphQL
539
631
  end
540
632
 
541
633
  def resolve_batch(objects, context, args_hash)
542
- method_receiver = @field_definition.dynamic_introspection ? @field_definition.owner : @parent_type
634
+ dyn_ins = @field_definition.dynamic_introspection
635
+ method_receiver = dyn_ins ? @field_definition.owner : @parent_type
543
636
  case @field_definition.execution_mode
544
637
  when :resolve_batch
545
638
  begin
546
639
  method_receiver.public_send(@field_definition.execution_mode_key, objects, context, **args_hash)
547
640
  rescue GraphQL::ExecutionError => exec_err
548
- Array.new(objects.size, 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
549
648
  end
550
649
  when :resolve_static
551
650
  result = begin
552
651
  method_receiver.public_send(@field_definition.execution_mode_key, context, **args_hash)
553
652
  rescue GraphQL::ExecutionError => err
554
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
555
660
  end
556
661
  Array.new(objects.size, result)
557
662
  when :resolve_each
558
663
  objects.map do |o|
559
- method_receiver.public_send(@field_definition.execution_mode_key, o, context, **args_hash)
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)
560
666
  rescue GraphQL::ExecutionError => err
561
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
562
674
  end
563
675
  when :hash_key
564
676
  k = @field_definition.execution_mode_key
@@ -571,7 +683,7 @@ module GraphQL
571
683
  err
572
684
  rescue StandardError => stderr
573
685
  begin
574
- @selections_step.query.handle_or_reraise(stderr)
686
+ @selections_step.query.handle_or_reraise(stderr, object: o, field: @field_definition, arguments: args_hash)
575
687
  rescue GraphQL::ExecutionError => ex_err
576
688
  ex_err
577
689
  end
@@ -615,9 +727,6 @@ module GraphQL
615
727
  results
616
728
  when :resolve_legacy_instance_method
617
729
  @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
730
  obj_inst.public_send(@field_definition.execution_mode_key, **args_hash)
622
731
  rescue GraphQL::ExecutionError => exec_err
623
732
  exec_err
@@ -626,6 +735,10 @@ module GraphQL
626
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"
627
736
  end
628
737
  end
738
+
739
+ def error_instance_array(size, err_prototype)
740
+ Array.new(size) { err_prototype.dup }
741
+ end
629
742
  end
630
743
  end
631
744
  end
@@ -117,7 +117,7 @@ module GraphQL
117
117
  @current_exec_path << key
118
118
  @current_result_path << key
119
119
 
120
- field_defn = @query.context.types.field(parent_type, ast_selection.name) || raise("Invariant: No field found for #{static_type.to_type_signature}.#{ast_selection.name}")
120
+ field_defn = @query.context.types.field(parent_type, ast_selection.name) || raise("Invariant: No field found for #{parent_type.to_type_signature}.#{ast_selection.name}")
121
121
  result_type = field_defn.type
122
122
  if (result_type_non_null = result_type.non_null?)
123
123
  result_type = result_type.of_type
@@ -154,12 +154,14 @@ module GraphQL
154
154
  @runner.type_condition_applies?(@query.context, static_type_at_result, t.name)
155
155
  )
156
156
  result_h = check_object_result(result_h, parent_type, ast_selection.selections)
157
+ return nil if result_h.nil?
157
158
  end
158
159
  when Language::Nodes::FragmentSpread
159
160
  fragment_defn = @query.document.definitions.find { |defn| defn.is_a?(Language::Nodes::FragmentDefinition) && defn.name == ast_selection.name }
160
161
  static_type_at_result = @static_type_at[result_h]
161
162
  if static_type_at_result && @runner.type_condition_applies?(@query.context, static_type_at_result, fragment_defn.type.name)
162
163
  result_h = check_object_result(result_h, parent_type, fragment_defn.selections)
164
+ return nil if result_h.nil?
163
165
  end
164
166
  end
165
167
  end
@@ -181,11 +183,20 @@ module GraphQL
181
183
  return result_arr if @finalizers_count == 0
182
184
  end
183
185
 
184
- result_arr.each_with_index do |result_item, idx|
185
- @current_result_path << idx
186
- new_result = if (f = finalizers(result_arr, idx))
187
- run_finalizers(@current_result_path.dup, f, result_arr, idx)
188
- result_arr[idx]
186
+ effective_idx = -1
187
+ result_arr.each_with_index do |result_item, before_idx|
188
+ effective_idx += 1
189
+ @current_result_path << before_idx
190
+ new_result = if (f = finalizers(result_arr, before_idx))
191
+ before_size = result_arr.size
192
+ run_finalizers(@current_result_path.dup, f, result_arr, effective_idx)
193
+ after_size = result_arr.size
194
+ if after_size < before_size
195
+ effective_idx -= 1
196
+ :unassigned
197
+ else
198
+ result_arr[effective_idx]
199
+ end
189
200
  elsif inner_type.list? && result_item
190
201
  check_list_result(result_item, inner_type.of_type, ast_selections)
191
202
  elsif !inner_type.kind.leaf? && result_item
@@ -196,10 +207,12 @@ module GraphQL
196
207
 
197
208
  if new_result.nil? && inner_type_non_null
198
209
  new_invalid_null = true
199
- result_arr[idx] = nil
210
+ result_arr[effective_idx] = nil
211
+ break if @finalizers_count == 0
212
+ elsif :unassigned.equal?(new_result)
200
213
  break if @finalizers_count == 0
201
214
  elsif !new_result.equal?(result_item)
202
- result_arr[idx] = new_result
215
+ result_arr[effective_idx] = new_result
203
216
  break if @finalizers_count == 0
204
217
  end
205
218
  ensure