graphql 2.6.1 → 2.6.2

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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/dataloader.rb +1 -1
  3. data/lib/graphql/execution/field_resolve_step.rb +165 -65
  4. data/lib/graphql/execution/finalize.rb +18 -7
  5. data/lib/graphql/execution/input_values.rb +110 -38
  6. data/lib/graphql/execution/interpreter/runtime.rb +36 -15
  7. data/lib/graphql/execution/load_argument_step.rb +35 -3
  8. data/lib/graphql/execution/next.rb +20 -12
  9. data/lib/graphql/execution/prepare_object_step.rb +18 -5
  10. data/lib/graphql/execution/resolve_type_step.rb +27 -0
  11. data/lib/graphql/execution/runner.rb +64 -29
  12. data/lib/graphql/execution/selections_step.rb +1 -1
  13. data/lib/graphql/execution.rb +8 -1
  14. data/lib/graphql/execution_error.rb +6 -12
  15. data/lib/graphql/introspection/entry_points.rb +2 -2
  16. data/lib/graphql/introspection/schema_type.rb +6 -2
  17. data/lib/graphql/language/lexer.rb +1 -1
  18. data/lib/graphql/language/parser.rb +1 -1
  19. data/lib/graphql/pagination/connections.rb +1 -3
  20. data/lib/graphql/query.rb +2 -2
  21. data/lib/graphql/schema/argument.rb +2 -2
  22. data/lib/graphql/schema/directive/feature.rb +4 -0
  23. data/lib/graphql/schema/directive/transform.rb +20 -0
  24. data/lib/graphql/schema/has_single_input_argument.rb +24 -13
  25. data/lib/graphql/schema/input_object.rb +4 -0
  26. data/lib/graphql/schema/ractor_shareable.rb +1 -0
  27. data/lib/graphql/schema/relay_classic_mutation.rb +16 -2
  28. data/lib/graphql/schema/resolver.rb +0 -7
  29. data/lib/graphql/schema/subscription.rb +53 -8
  30. data/lib/graphql/schema/timeout.rb +2 -2
  31. data/lib/graphql/schema/visibility/visit.rb +1 -1
  32. data/lib/graphql/schema.rb +30 -9
  33. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +6 -0
  34. data/lib/graphql/subscriptions/event.rb +0 -1
  35. data/lib/graphql/tracing/perfetto_trace.rb +5 -3
  36. data/lib/graphql/version.rb +1 -1
  37. 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: a0c0b5ff2af84e07dab381443b1b62137074324bb3063c8387bf6dc777d51d00
4
+ data.tar.gz: 4e5383e45c9d62ea5a04782867a743d01d6db0d0bc853deadc7a229f26d440e4
5
5
  SHA512:
6
- metadata.gz: bca56323051f3df8cc6d0ce3d91ee0ba0c125cda0b2e2ddf5deab0d18131cdca646aa7a8b3a0dfee98abd5ea6be25eee358ecd3c08ab885c31c946af8033ec05
7
- data.tar.gz: d4e993d8b2a3492113bb00175ddaed842ee6d10ca948d7c53ed6550007f7bd24c784214557bf4c33a35139a73909575bc9b93a2bff41d66dfdeeb235e8b4d00f
6
+ metadata.gz: 86019205d2eb2dabd464dc615af28ab078c1a48e43c0701a1852dddfd13eded8954e42e91b7176c685eed10679b15bf4bcef2c86c48d9f9ad4b870cdc78bb19a
7
+ data.tar.gz: b95880ff00058b167fe7574ca5e267814fe7aa9be65df0d2691d658344a796dd1fdafedad6cc35b58c3299c5401b39a964353cb5ab0e3974296ca4c26f9ab504
@@ -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
@@ -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,
@@ -69,7 +69,7 @@ module GraphQL
69
69
  err
70
70
  rescue StandardError => stderr
71
71
  begin
72
- @selections_step.query.handle_or_reraise(stderr)
72
+ @selections_step.query.handle_or_reraise(stderr, field: @field_definition, arguments: @arguments, object: nil)
73
73
  rescue GraphQL::ExecutionError => ex_err
74
74
  ex_err
75
75
  end
@@ -98,17 +98,34 @@ module GraphQL
98
98
 
99
99
  def add_graphql_error(err)
100
100
  err.path = path
101
- err.ast_nodes = ast_nodes
101
+ if err.ast_node.nil?
102
+ err.ast_nodes = ast_nodes
103
+ end
102
104
  @selections_step.query.context.add_error(err)
103
105
  err
104
106
  end
105
107
 
108
+ def build_errors_result(errors, single_error)
109
+ first_error = errors.nil? ? single_error : errors.pop
110
+ @field_results = error_instance_array(@selections_step.objects.size, first_error)
111
+ if errors
112
+ errors.each do |e|
113
+ add_graphql_error(e)
114
+ end
115
+ end
116
+ @results ||= @selections_step.results
117
+ build_results
118
+ end
119
+
106
120
  def build_arguments
107
121
  query = @selections_step.query
108
122
  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
123
+ @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})")
124
+ @arguments, errors = @runner.input_values[query].argument_values(@field_definition, @ast_node.arguments, self) # rubocop:disable Development/ContextIsPassedCop
125
+ if errors
126
+ build_errors_result(errors, nil)
127
+ return
128
+ end
112
129
 
113
130
  if (@pending_steps.nil? || @pending_steps.size == 0) &&
114
131
  @field_results.nil? # Make sure the arguments flow didn't already call through
@@ -116,24 +133,29 @@ module GraphQL
116
133
  end
117
134
  end
118
135
 
136
+ # Used for compatibility in Schema::Subscription
137
+ def arguments_without_loads
138
+ if @arguments_without_loads.nil?
139
+ @arguments_without_loads, _errors = @runner.input_values[@selections_step.query].argument_values(@field_definition, ast_node.arguments, nil)
140
+ end
141
+ @arguments_without_loads
142
+ end
143
+
119
144
  def execute_field
120
145
  objects = @selections_step.objects
121
- @results = @selections_step.results
122
- # TODO not as good because only one error?
123
146
  if @arguments.is_a?(GraphQL::RuntimeError)
124
- @field_results = Array.new(objects.size, @arguments)
125
- build_results
147
+ build_errors_result(nil, @arguments)
126
148
  return
127
149
  end
128
150
 
151
+ @results = @selections_step.results
129
152
  query = @selections_step.query
130
153
  ctx = query.context
131
154
  if (v = @field_definition.validators).any? # rubocop:disable Development/NoneWithoutBlockCop
132
155
  begin
133
156
  Schema::Validator.validate!(v, nil, ctx, @arguments)
134
157
  rescue GraphQL::RuntimeError => err
135
- @field_results = Array.new(objects.size, err)
136
- build_results
158
+ build_errors_result(nil, err)
137
159
  return
138
160
  end
139
161
  end
@@ -160,25 +182,40 @@ module GraphQL
160
182
  end
161
183
 
162
184
  if @field_definition.dynamic_introspection
163
- # TODO break this backwards compat somehow?
164
- objects = @selections_step.graphql_objects
185
+ objects = @selections_step.graphql_objects.map { |o| @field_definition.owner.wrap(o, ctx) }
165
186
  end
166
187
 
167
- if @runner.authorization && @runner.authorizes?(@field_definition, ctx)
188
+ if @runner.authorizes?(@field_definition, ctx)
168
189
  authorized_objects = []
169
190
  authorized_results = []
170
191
  l = objects.size
171
192
  i = 0
172
193
  while i < l
173
194
  o = objects[i]
174
- if @field_definition.authorized?(o, @arguments, ctx)
195
+ err = nil
196
+ begin
197
+ field_authed = @field_definition.authorized?(o, @arguments, ctx)
198
+ if @runner.resolves_lazies && @runner.lazy?(field_authed)
199
+ # TODO batch this properly...
200
+ field_authed = sync(field_authed)
201
+ end
202
+ rescue GraphQL::UnauthorizedFieldError => field_auth_err
203
+ err = field_auth_err
204
+ err.field ||= @field_definition
205
+ field_authed = false
206
+ end
207
+
208
+ if field_authed
175
209
  authorized_results << @results[i]
176
210
  authorized_objects << o
177
211
  else
178
212
  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]
213
+ err ||= GraphQL::UnauthorizedFieldError.new(object: o, type: @parent_type, context: ctx, field: @field_definition)
214
+ new_obj = query.schema.unauthorized_field(err)
215
+ if !new_obj.nil?
216
+ authorized_objects << new_obj
217
+ authorized_results << @results[i]
218
+ end
182
219
  rescue GraphQL::ExecutionError => exec_err
183
220
  add_graphql_error(exec_err)
184
221
  end
@@ -220,27 +257,37 @@ module GraphQL
220
257
  if directives
221
258
  directives.each do |dir_node|
222
259
  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
260
+ dir_args, errors = @runner.input_values[query].argument_values(dir_defn, dir_node.arguments, self) # rubocop:disable Development/ContextIsPassedCop
261
+ if errors
262
+ @results.each { |r| r.delete(@key) }
263
+ errors.each { |e| e.ast_node = dir_node }
264
+ build_errors_result(errors, nil)
265
+ return
266
+ else
267
+ begin
268
+ dir_defn.validate!(dir_args, query.context)
269
+ if !(result = dir_defn.resolve_field(ast_nodes, @parent_type, field_definition, authorized_objects, dir_args, ctx)).nil?
270
+ if result.is_a?(Finalizer)
271
+ result.path = path
272
+ @directive_finalizers ||= []
273
+ @directive_finalizers << result
274
+ end
275
+
276
+ if result.is_a?(PostProcessor)
277
+ @post_processors ||= []
278
+ @post_processors << result
279
+ end
280
+
281
+ if result.is_a?(HaltExecution)
282
+ @directive_finalizers&.each { |f|
283
+ @selections_step.results.each { |r| @runner.add_finalizer(query, r, key, f) }
284
+ }
285
+ return
286
+ end
287
+ end
288
+ rescue GraphQL::RuntimeError => err
289
+ err.ast_node = dir_node
290
+ raise
244
291
  end
245
292
  end
246
293
  end
@@ -267,10 +314,20 @@ module GraphQL
267
314
 
268
315
  if any_lazy_results?
269
316
  @runner.dataloader.lazy_at_depth(path.size, self)
270
- elsif has_extensions
271
- finish_extensions
272
317
  elsif @pending_steps.nil? || @pending_steps.empty?
273
- build_results
318
+ if has_extensions
319
+ finish_extensions
320
+ else
321
+ build_results
322
+ end
323
+ end
324
+ rescue GraphQL::ExecutionError => err
325
+ add_graphql_error(err)
326
+ rescue StandardError => stderr
327
+ begin
328
+ @selections_step.query.handle_or_reraise(stderr, field: @field_definition, arguments: @arguments, object: nil)
329
+ rescue GraphQL::ExecutionError => err
330
+ add_graphql_error(err)
274
331
  end
275
332
  end
276
333
 
@@ -312,6 +369,8 @@ module GraphQL
312
369
  conn.was_authorized_by_scope_items = @was_scoped
313
370
  end
314
371
  conn
372
+ rescue GraphQL::RuntimeError => err
373
+ err
315
374
  end
316
375
  when Schema::Field::ScopeExtension
317
376
  if @was_scoped.nil?
@@ -390,9 +449,16 @@ module GraphQL
390
449
  end
391
450
 
392
451
  def finish_leaf_result(result_h, key, field_result, return_type, ctx)
393
- final_field_result = if field_result.nil?
452
+ final_field_result = build_leaf_result(field_result, return_type, ctx, false)
453
+
454
+ @directive_finalizers&.each { |f| @runner.add_finalizer(ctx.query, result_h, key, f) }
455
+ result_h[@key] = final_field_result
456
+ end
457
+
458
+ def build_leaf_result(field_result, return_type, ctx, is_from_array)
459
+ if field_result.nil?
394
460
  if return_type.non_null?
395
- add_non_null_error(false)
461
+ add_non_null_error(is_from_array)
396
462
  else
397
463
  nil
398
464
  end
@@ -403,13 +469,16 @@ module GraphQL
403
469
  field_result.path = path
404
470
  @runner.add_finalizer(ctx.query, result_h, key, field_result)
405
471
  end
472
+ elsif return_type.list?
473
+ if return_type.non_null?
474
+ return_type = return_type.of_type
475
+ end
476
+
477
+ inner_type = return_type.of_type
478
+ field_result.map { |item| build_leaf_result(item, inner_type, ctx, true) }
406
479
  else
407
- # TODO `nil`s in [T!] types aren't handled
408
480
  return_type.coerce_result(field_result, ctx)
409
481
  end
410
-
411
- @directive_finalizers&.each { |f| @runner.add_finalizer(ctx.query, result_h, key, f) }
412
- result_h[@key] = final_field_result
413
482
  end
414
483
 
415
484
  def enqueue_next_steps
@@ -427,7 +496,17 @@ module GraphQL
427
496
  if (object_type = @runner.runtime_type_at[result])
428
497
  # OK
429
498
  else
430
- object_type = @runner.resolve_type(@static_type, next_object, query)
499
+ query.current_trace.begin_resolve_type(@static_type, next_object, query.context)
500
+ object_type = ResolveTypeStep.resolve_type(@static_type, next_object, query)
501
+ if object_type.is_a?(Array)
502
+ object_type, next_object = object_type
503
+ end
504
+ if @runner.resolves_lazies && @runner.lazy?(object_type)
505
+ # TODO batch this
506
+ object_type, next_object = sync(object_type)
507
+ end
508
+ ResolveTypeStep.assert_valid_resolved_type(@static_type, object_type, next_object, self)
509
+ query.current_trace.end_resolve_type(@static_type, next_object, query.context, object_type)
431
510
  @runner.runtime_type_at[result] = object_type
432
511
  end
433
512
  next_objects_by_type[object_type] << next_object
@@ -469,7 +548,7 @@ module GraphQL
469
548
  end
470
549
 
471
550
  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)
551
+ err = @parent_type::InvalidNullError.new(@parent_type, @field_definition, ast_nodes, is_from_array: is_from_array, path: path)
473
552
  @runner.schema.type_error(err, @selections_step.query.context)
474
553
  end
475
554
 
@@ -507,13 +586,13 @@ module GraphQL
507
586
  i += 1
508
587
  end
509
588
  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
- )))
589
+ @static_type.kind.object? ?
590
+ @runner.authorizes?(@static_type, @selections_step.query.context) :
591
+ (
592
+ (runtime_type, _ignored_new_value = ResolveTypeStep.resolve_type(@static_type, field_result, @selections_step.query)) &&
593
+ (@runner.runtime_type_at[graphql_result] = runtime_type) &&
594
+ @runner.authorizes?(runtime_type, @selections_step.query.context)
595
+ ))
517
596
  obj_step = PrepareObjectStep.new(
518
597
  object: field_result,
519
598
  runner: @runner,
@@ -539,26 +618,46 @@ module GraphQL
539
618
  end
540
619
 
541
620
  def resolve_batch(objects, context, args_hash)
542
- method_receiver = @field_definition.dynamic_introspection ? @field_definition.owner : @parent_type
621
+ dyn_ins = @field_definition.dynamic_introspection
622
+ method_receiver = dyn_ins ? @field_definition.owner : @parent_type
543
623
  case @field_definition.execution_mode
544
624
  when :resolve_batch
545
625
  begin
546
626
  method_receiver.public_send(@field_definition.execution_mode_key, objects, context, **args_hash)
547
627
  rescue GraphQL::ExecutionError => exec_err
548
- Array.new(objects.size, exec_err)
628
+ error_instance_array(objects.size, exec_err)
629
+ rescue StandardError => stderr
630
+ begin
631
+ context.query.handle_or_reraise(stderr, field: @field_definition, arguments: @arguments, object: nil)
632
+ rescue GraphQL::ExecutionError => exec_err
633
+ error_instance_array(objects.size, exec_err)
634
+ end
549
635
  end
550
636
  when :resolve_static
551
637
  result = begin
552
638
  method_receiver.public_send(@field_definition.execution_mode_key, context, **args_hash)
553
639
  rescue GraphQL::ExecutionError => err
554
640
  err
641
+ rescue StandardError => stderr
642
+ begin
643
+ context.query.handle_or_reraise(stderr, field: @field_definition, arguments: @arguments, object: nil)
644
+ rescue GraphQL::ExecutionError => err
645
+ err
646
+ end
555
647
  end
556
648
  Array.new(objects.size, result)
557
649
  when :resolve_each
558
650
  objects.map do |o|
559
- method_receiver.public_send(@field_definition.execution_mode_key, o, context, **args_hash)
651
+ passed_in_obj = dyn_ins ? o.object : o
652
+ method_receiver.public_send(@field_definition.execution_mode_key, passed_in_obj, context, **args_hash)
560
653
  rescue GraphQL::ExecutionError => err
561
654
  err
655
+ rescue StandardError => stderr
656
+ begin
657
+ context.query.handle_or_reraise(stderr, field: @field_definition, arguments: @arguments, object: o)
658
+ rescue GraphQL::ExecutionError => err
659
+ err
660
+ end
562
661
  end
563
662
  when :hash_key
564
663
  k = @field_definition.execution_mode_key
@@ -571,7 +670,7 @@ module GraphQL
571
670
  err
572
671
  rescue StandardError => stderr
573
672
  begin
574
- @selections_step.query.handle_or_reraise(stderr)
673
+ @selections_step.query.handle_or_reraise(stderr, object: o, field: @field_definition, arguments: args_hash)
575
674
  rescue GraphQL::ExecutionError => ex_err
576
675
  ex_err
577
676
  end
@@ -615,9 +714,6 @@ module GraphQL
615
714
  results
616
715
  when :resolve_legacy_instance_method
617
716
  @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
717
  obj_inst.public_send(@field_definition.execution_mode_key, **args_hash)
622
718
  rescue GraphQL::ExecutionError => exec_err
623
719
  exec_err
@@ -626,6 +722,10 @@ module GraphQL
626
722
  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
723
  end
628
724
  end
725
+
726
+ def error_instance_array(size, err_prototype)
727
+ Array.new(size) { err_prototype.dup }
728
+ end
629
729
  end
630
730
  end
631
731
  end
@@ -181,11 +181,20 @@ module GraphQL
181
181
  return result_arr if @finalizers_count == 0
182
182
  end
183
183
 
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]
184
+ effective_idx = -1
185
+ result_arr.each_with_index do |result_item, before_idx|
186
+ effective_idx += 1
187
+ @current_result_path << before_idx
188
+ new_result = if (f = finalizers(result_arr, before_idx))
189
+ before_size = result_arr.size
190
+ run_finalizers(@current_result_path.dup, f, result_arr, effective_idx)
191
+ after_size = result_arr.size
192
+ if after_size < before_size
193
+ effective_idx -= 1
194
+ :unassigned
195
+ else
196
+ result_arr[effective_idx]
197
+ end
189
198
  elsif inner_type.list? && result_item
190
199
  check_list_result(result_item, inner_type.of_type, ast_selections)
191
200
  elsif !inner_type.kind.leaf? && result_item
@@ -196,10 +205,12 @@ module GraphQL
196
205
 
197
206
  if new_result.nil? && inner_type_non_null
198
207
  new_invalid_null = true
199
- result_arr[idx] = nil
208
+ result_arr[effective_idx] = nil
209
+ break if @finalizers_count == 0
210
+ elsif :unassigned.equal?(new_result)
200
211
  break if @finalizers_count == 0
201
212
  elsif !new_result.equal?(result_item)
202
- result_arr[idx] = new_result
213
+ result_arr[effective_idx] = new_result
203
214
  break if @finalizers_count == 0
204
215
  end
205
216
  ensure