graphql 2.5.22 → 2.5.24

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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/analysis.rb +20 -13
  3. data/lib/graphql/execution/field_resolve_step.rb +631 -0
  4. data/lib/graphql/execution/finalize.rb +217 -0
  5. data/lib/graphql/execution/input_values.rb +261 -0
  6. data/lib/graphql/execution/interpreter/handles_raw_value.rb +6 -0
  7. data/lib/graphql/execution/interpreter/runtime.rb +3 -2
  8. data/lib/graphql/execution/interpreter.rb +6 -9
  9. data/lib/graphql/execution/lazy.rb +1 -1
  10. data/lib/graphql/execution/load_argument_step.rb +64 -0
  11. data/lib/graphql/execution/next.rb +26 -6
  12. data/lib/graphql/execution/prepare_object_step.rb +128 -0
  13. data/lib/graphql/execution/runner.rb +410 -0
  14. data/lib/graphql/execution/selections_step.rb +91 -0
  15. data/lib/graphql/execution.rb +8 -5
  16. data/lib/graphql/execution_error.rb +5 -1
  17. data/lib/graphql/query/context.rb +7 -1
  18. data/lib/graphql/query/partial.rb +18 -3
  19. data/lib/graphql/query.rb +10 -1
  20. data/lib/graphql/runtime_error.rb +6 -0
  21. data/lib/graphql/schema/directive.rb +23 -9
  22. data/lib/graphql/schema/field/connection_extension.rb +2 -15
  23. data/lib/graphql/schema/field/scope_extension.rb +0 -4
  24. data/lib/graphql/schema/field.rb +23 -24
  25. data/lib/graphql/schema/field_extension.rb +11 -41
  26. data/lib/graphql/schema/interface.rb +26 -0
  27. data/lib/graphql/schema/list.rb +5 -1
  28. data/lib/graphql/schema/member/base_dsl_methods.rb +0 -10
  29. data/lib/graphql/schema/member/has_fields.rb +5 -1
  30. data/lib/graphql/schema/non_null.rb +1 -1
  31. data/lib/graphql/schema/resolver.rb +18 -3
  32. data/lib/graphql/schema/subscription.rb +0 -2
  33. data/lib/graphql/schema/visibility/profile.rb +68 -49
  34. data/lib/graphql/schema/wrapper.rb +7 -1
  35. data/lib/graphql/schema.rb +12 -10
  36. data/lib/graphql/static_validation/base_visitor.rb +90 -66
  37. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  38. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +18 -6
  39. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +5 -2
  40. data/lib/graphql/static_validation/rules/directives_are_defined.rb +5 -2
  41. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +4 -3
  42. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +12 -2
  43. data/lib/graphql/static_validation/rules/fields_will_merge.rb +322 -256
  44. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +4 -4
  45. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  46. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +10 -7
  47. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +27 -7
  48. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +12 -9
  49. data/lib/graphql/static_validation/validation_context.rb +1 -1
  50. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +34 -10
  51. data/lib/graphql/subscriptions/event.rb +1 -0
  52. data/lib/graphql/subscriptions.rb +35 -0
  53. data/lib/graphql/tracing/perfetto_trace.rb +2 -2
  54. data/lib/graphql/tracing/trace.rb +6 -0
  55. data/lib/graphql/unauthorized_error.rb +4 -0
  56. data/lib/graphql/version.rb +1 -1
  57. data/lib/graphql.rb +1 -3
  58. metadata +11 -8
  59. data/lib/graphql/execution/next/field_resolve_step.rb +0 -711
  60. data/lib/graphql/execution/next/load_argument_step.rb +0 -60
  61. data/lib/graphql/execution/next/prepare_object_step.rb +0 -129
  62. data/lib/graphql/execution/next/runner.rb +0 -389
  63. data/lib/graphql/execution/next/selections_step.rb +0 -37
@@ -28,10 +28,10 @@ module GraphQL
28
28
  end
29
29
 
30
30
  def add_conflict(node, conflict_str)
31
- # Can't use `.include?` here because AST nodes implement `#==`
32
- # based on string value, not including location. But sometimes,
33
- # identical nodes conflict because of their differing return types.
34
- if nodes.any? { |n| n == node && n.line == node.line && n.col == node.col }
31
+ # Check if we already have an error for this exact node.
32
+ # Use object identity first (fast path), then fall back to
33
+ # value + location comparison for duplicate AST nodes.
34
+ if nodes.any? { |n| n.equal?(node) || (n.line == node.line && n.col == node.col && n == node) }
35
35
  # already have an error for this node
36
36
  return
37
37
  end
@@ -8,8 +8,8 @@ module GraphQL
8
8
  end
9
9
 
10
10
  def on_inline_fragment(node, parent)
11
- fragment_parent = context.object_types[-2]
12
- fragment_child = context.object_types.last
11
+ fragment_parent = @parent_object_type
12
+ fragment_child = @current_object_type
13
13
  if fragment_child
14
14
  validate_fragment_in_scope(fragment_parent, fragment_child, node, context, context.path)
15
15
  end
@@ -17,7 +17,7 @@ module GraphQL
17
17
  end
18
18
 
19
19
  def on_fragment_spread(node, parent)
20
- fragment_parent = context.object_types.last
20
+ fragment_parent = @current_object_type
21
21
  @spreads_to_validate << FragmentSpread.new(node: node, parent_type: fragment_parent, path: context.path)
22
22
  super
23
23
  end
@@ -23,18 +23,21 @@ module GraphQL
23
23
  type_name = fragment_node.type.name
24
24
  type = @types.type(type_name)
25
25
  if type.nil?
26
- @all_possible_fragment_type_names ||= begin
27
- names = []
28
- context.types.all_types.each do |type|
29
- if type.kind.fields?
30
- names << type.graphql_name
26
+ suggestion = if @schema.did_you_mean
27
+ @all_possible_fragment_type_names ||= begin
28
+ names = []
29
+ context.types.all_types.each do |type|
30
+ if type.kind.fields?
31
+ names << type.graphql_name
32
+ end
31
33
  end
34
+ names
32
35
  end
33
- names
36
+ context.did_you_mean_suggestion(type_name, @all_possible_fragment_type_names)
34
37
  end
35
38
 
36
39
  add_error(GraphQL::StaticValidation::FragmentTypesExistError.new(
37
- "No such type #{type_name}, so it can't be a fragment condition#{context.did_you_mean_suggestion(type_name, @all_possible_fragment_type_names)}",
40
+ "No such type #{type_name}, so it can't be a fragment condition#{suggestion}",
38
41
  nodes: fragment_node,
39
42
  type: type_name
40
43
  ))
@@ -2,8 +2,13 @@
2
2
  module GraphQL
3
3
  module StaticValidation
4
4
  module RequiredArgumentsArePresent
5
+ def initialize(*)
6
+ super
7
+ @required_args_cache = {}.compare_by_identity
8
+ end
9
+
5
10
  def on_field(node, _parent)
6
- assert_required_args(node, field_definition)
11
+ assert_required_args(node, @current_field_definition)
7
12
  super
8
13
  end
9
14
 
@@ -16,13 +21,28 @@ module GraphQL
16
21
  private
17
22
 
18
23
  def assert_required_args(ast_node, defn)
19
- args = @context.query.types.arguments(defn)
20
- return if args.empty?
21
- present_argument_names = ast_node.arguments.map(&:name)
22
- required_argument_names = context.query.types.arguments(defn)
23
- .select { |a| a.type.kind.non_null? && !a.default_value? && context.query.types.argument(defn, a.name) }
24
- .map!(&:name)
24
+ return unless defn
25
25
 
26
+ # Cache required argument names per definition to avoid re-iterating
27
+ # arguments for the same definition across field instances
28
+ if @required_args_cache.key?(defn)
29
+ required_argument_names = @required_args_cache[defn]
30
+ else
31
+ args = @types.arguments(defn)
32
+ required_argument_names = nil
33
+ if !args.empty?
34
+ args.each do |a|
35
+ if a.type.kind.non_null? && !a.default_value? && @types.argument(defn, a.name)
36
+ (required_argument_names ||= []) << a.graphql_name
37
+ end
38
+ end
39
+ end
40
+ @required_args_cache[defn] = required_argument_names
41
+ end
42
+
43
+ return if required_argument_names.nil?
44
+
45
+ present_argument_names = ast_node.arguments.map(&:name)
26
46
  missing_names = required_argument_names - present_argument_names
27
47
  if !missing_names.empty?
28
48
  add_error(GraphQL::StaticValidation::RequiredArgumentsArePresentError.new(
@@ -7,17 +7,20 @@ module GraphQL
7
7
  type = context.query.types.type(type_name)
8
8
 
9
9
  if type.nil?
10
- @all_possible_input_type_names ||= begin
11
- names = []
12
- context.types.all_types.each { |(t)|
13
- if t.kind.input?
14
- names << t.graphql_name
15
- end
16
- }
17
- names
10
+ suggestion = if @schema.did_you_mean
11
+ @all_possible_input_type_names ||= begin
12
+ names = []
13
+ context.types.all_types.each { |(t)|
14
+ if t.kind.input?
15
+ names << t.graphql_name
16
+ end
17
+ }
18
+ names
19
+ end
20
+ context.did_you_mean_suggestion(type_name, @all_possible_input_type_names)
18
21
  end
19
22
  add_error(GraphQL::StaticValidation::VariablesAreInputTypesError.new(
20
- "#{type_name} isn't a defined input type (on $#{node.name})#{context.did_you_mean_suggestion(type_name, @all_possible_input_type_names)}",
23
+ "#{type_name} isn't a defined input type (on $#{node.name})#{suggestion}",
21
24
  nodes: node,
22
25
  name: node.name,
23
26
  type: type_name
@@ -32,7 +32,7 @@ module GraphQL
32
32
  # TODO stop using def_delegators because of Array allocations
33
33
  def_delegators :@visitor,
34
34
  :path, :type_definition, :field_definition, :argument_definition,
35
- :parent_type_definition, :directive_definition, :object_types, :dependencies
35
+ :parent_type_definition, :directive_definition, :dependencies
36
36
 
37
37
  def on_dependency_resolve(&handler)
38
38
  @on_dependency_resolve_handlers << handler
@@ -2,25 +2,49 @@
2
2
  module GraphQL
3
3
  class Subscriptions
4
4
  class DefaultSubscriptionResolveExtension < GraphQL::Schema::FieldExtension
5
- def resolve(context:, object:, arguments:)
6
- has_override_implementation = @field.resolver ||
7
- object.respond_to?(@field.resolver_method)
5
+ def resolve(context:, object: nil, objects: nil, arguments:)
6
+ if objects
7
+ has_override_implementation = @field.execution_mode != :direct_send
8
8
 
9
- if !has_override_implementation
10
- if context.query.subscription_update?
11
- object.object
9
+ if !has_override_implementation
10
+ if context.query.subscription_update?
11
+ objects
12
+ else
13
+ objects.map { |o| context.skip }
14
+ end
12
15
  else
13
- context.skip
16
+ yield(objects, arguments)
14
17
  end
15
18
  else
16
- yield(object, arguments)
19
+ has_override_implementation = @field.resolver ||
20
+ object.respond_to?(@field.resolver_method)
21
+
22
+ if !has_override_implementation
23
+ if context.query.subscription_update?
24
+ object.object
25
+ else
26
+ context.skip
27
+ end
28
+ else
29
+ yield(object, arguments)
30
+ end
31
+ end
32
+ end
33
+
34
+ def after_resolve(values: nil, value: nil, context:, objects: nil, object: nil, arguments:, **rest)
35
+ if values
36
+ values.map do |value|
37
+ self.class.write_subscription(@field, value, arguments, context)
38
+ end
39
+ else
40
+ self.class.write_subscription(@field, value, arguments, context)
17
41
  end
18
42
  end
19
43
 
20
- def after_resolve(value:, context:, object:, arguments:, **rest)
44
+ def self.write_subscription(field, value, arguments, context)
21
45
  if value.is_a?(GraphQL::ExecutionError)
22
46
  value
23
- elsif @field.resolver&.method_defined?(:subscription_written?) &&
47
+ elsif field.resolver&.method_defined?(:subscription_written?) &&
24
48
  (subscription_namespace = context.namespace(:subscriptions)) &&
25
49
  (subscriptions_by_path = subscription_namespace[:subscriptions])
26
50
  (subscription_instance = subscriptions_by_path[context.current_path])
@@ -104,6 +104,7 @@ module GraphQL
104
104
 
105
105
  def stringify_args(arg_owner, args, context)
106
106
  arg_owner = arg_owner.respond_to?(:unwrap) ? arg_owner.unwrap : arg_owner # remove list and non-null wrappers
107
+
107
108
  case args
108
109
  when Hash
109
110
  next_args = {}
@@ -239,6 +239,41 @@ module GraphQL
239
239
  query.context.namespace(:subscriptions)[:subscription_broadcastable]
240
240
  end
241
241
 
242
+ # Called during execution when a new `subscription ...` operation is received
243
+ # @param query [GraphQL::Query]
244
+ # @return [void]
245
+ def initialize_subscriptions(query)
246
+ subs_namespace = query.context.namespace(:subscriptions)
247
+ subs_namespace[:events] = []
248
+ subs_namespace[:subscriptions] = {}
249
+ nil
250
+ end
251
+
252
+ # Called during execution when a subscription operation has finished
253
+ # @param query [GraphQL::Query]
254
+ # @return [void]
255
+ def finish_subscriptions(query)
256
+ if (events = query.context.namespace(:subscriptions)[:events]) && !events.empty?
257
+ write_subscription(query, events)
258
+ end
259
+ nil
260
+ end
261
+
262
+ def finalizer
263
+ Finalizer.new(self)
264
+ end
265
+
266
+ class Finalizer
267
+ include Execution::Finalizer
268
+ def initialize(subscriptions)
269
+ @subscriptions = subscriptions
270
+ end
271
+
272
+ def finalize_graphql_result(query, result_data, result_key)
273
+ @subscriptions.finish_subscriptions(query)
274
+ end
275
+ end
276
+
242
277
  private
243
278
 
244
279
  # Recursively normalize `args` as belonging to `arg_owner`:
@@ -253,7 +253,7 @@ module GraphQL
253
253
  packet = trace_packet(
254
254
  type: TrackEvent::Type::TYPE_SLICE_BEGIN,
255
255
  track_uuid: fid,
256
- name: query.context.current_path.join("."),
256
+ name: query.context.current_path&.join(".") || field.path,
257
257
  category_iids: FIELD_EXECUTE_CATEGORY_IIDS,
258
258
  extra_counter_track_uuids: @counts_objects,
259
259
  extra_counter_values: [count_allocations],
@@ -269,7 +269,7 @@ module GraphQL
269
269
  if @create_debug_annotations
270
270
  start_field.track_event = dup_with(start_field.track_event,{
271
271
  debug_annotations: [
272
- payload_to_debug(nil, object.object, iid: DA_OBJECT_IID, intern_value: true),
272
+ payload_to_debug(nil, (object.is_a?(GraphQL::Schema::Object) ? object.object : object), iid: DA_OBJECT_IID, intern_value: true),
273
273
  payload_to_debug(nil, arguments, iid: DA_ARGUMENTS_IID),
274
274
  payload_to_debug(nil, app_result, iid: DA_RESULT_IID, intern_value: true)
275
275
  ]
@@ -98,6 +98,12 @@ module GraphQL
98
98
  yield
99
99
  end
100
100
 
101
+ def objects(type, object, context)
102
+ end
103
+
104
+ def object_loaded(argument_definition, object, context)
105
+ end
106
+
101
107
  # A call to `.authorized?` is starting
102
108
  # @param type [Class<GraphQL::Schema::Object>]
103
109
  # @param object [Object]
@@ -29,5 +29,9 @@ module GraphQL
29
29
  end
30
30
 
31
31
  attr_accessor :path, :ast_nodes
32
+
33
+ def finalize_graphql_result(query, result_data, key)
34
+ result_data[key] = nil
35
+ end
32
36
  end
33
37
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "2.5.22"
3
+ VERSION = "2.5.24"
4
4
  end
data/lib/graphql.rb CHANGED
@@ -21,9 +21,6 @@ module GraphQL
21
21
  class Error < StandardError
22
22
  end
23
23
 
24
- class RuntimeError < Error
25
- end
26
-
27
24
  # This error is raised when GraphQL-Ruby encounters a situation
28
25
  # that it *thought* would never happen. Please report this bug!
29
26
  class InvariantError < Error
@@ -122,6 +119,7 @@ This is probably a bug in GraphQL-Ruby, please report this error on GitHub: http
122
119
  autoload :ParseError, "graphql/parse_error"
123
120
  autoload :Backtrace, "graphql/backtrace"
124
121
 
122
+ autoload :RuntimeError, "graphql/runtime_error"
125
123
  autoload :UnauthorizedError, "graphql/unauthorized_error"
126
124
  autoload :UnauthorizedEnumValueError, "graphql/unauthorized_enum_value_error"
127
125
  autoload :UnauthorizedFieldError, "graphql/unauthorized_field_error"
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.5.22
4
+ version: 2.5.24
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Mosolgo
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-03-23 00:00:00.000000000 Z
10
+ date: 2026-04-23 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: base64
@@ -493,6 +493,9 @@ files:
493
493
  - lib/graphql/execution.rb
494
494
  - lib/graphql/execution/directive_checks.rb
495
495
  - lib/graphql/execution/errors.rb
496
+ - lib/graphql/execution/field_resolve_step.rb
497
+ - lib/graphql/execution/finalize.rb
498
+ - lib/graphql/execution/input_values.rb
496
499
  - lib/graphql/execution/interpreter.rb
497
500
  - lib/graphql/execution/interpreter/argument_value.rb
498
501
  - lib/graphql/execution/interpreter/arguments.rb
@@ -504,14 +507,13 @@ files:
504
507
  - lib/graphql/execution/interpreter/runtime/graphql_result.rb
505
508
  - lib/graphql/execution/lazy.rb
506
509
  - lib/graphql/execution/lazy/lazy_method_map.rb
510
+ - lib/graphql/execution/load_argument_step.rb
507
511
  - lib/graphql/execution/lookahead.rb
508
512
  - lib/graphql/execution/multiplex.rb
509
513
  - lib/graphql/execution/next.rb
510
- - lib/graphql/execution/next/field_resolve_step.rb
511
- - lib/graphql/execution/next/load_argument_step.rb
512
- - lib/graphql/execution/next/prepare_object_step.rb
513
- - lib/graphql/execution/next/runner.rb
514
- - lib/graphql/execution/next/selections_step.rb
514
+ - lib/graphql/execution/prepare_object_step.rb
515
+ - lib/graphql/execution/runner.rb
516
+ - lib/graphql/execution/selections_step.rb
515
517
  - lib/graphql/execution_error.rb
516
518
  - lib/graphql/integer_decoding_error.rb
517
519
  - lib/graphql/integer_encoding_error.rb
@@ -577,6 +579,7 @@ files:
577
579
  - lib/graphql/rubocop/graphql/default_required_true.rb
578
580
  - lib/graphql/rubocop/graphql/field_type_in_block.rb
579
581
  - lib/graphql/rubocop/graphql/root_types_in_block.rb
582
+ - lib/graphql/runtime_error.rb
580
583
  - lib/graphql/runtime_type_error.rb
581
584
  - lib/graphql/schema.rb
582
585
  - lib/graphql/schema/addition.rb
@@ -831,7 +834,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
831
834
  - !ruby/object:Gem::Version
832
835
  version: '0'
833
836
  requirements: []
834
- rubygems_version: 4.0.8
837
+ rubygems_version: 4.0.6
835
838
  specification_version: 4
836
839
  summary: A GraphQL language and runtime for Ruby
837
840
  test_files: []