graphql 2.5.22 → 2.5.23

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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/execution/interpreter/runtime.rb +3 -2
  3. data/lib/graphql/execution/interpreter.rb +6 -9
  4. data/lib/graphql/execution/lazy.rb +1 -1
  5. data/lib/graphql/execution/next/field_resolve_step.rb +93 -61
  6. data/lib/graphql/execution/next/load_argument_step.rb +5 -1
  7. data/lib/graphql/execution/next/prepare_object_step.rb +2 -2
  8. data/lib/graphql/execution/next/runner.rb +48 -26
  9. data/lib/graphql/execution/next.rb +3 -1
  10. data/lib/graphql/execution.rb +7 -4
  11. data/lib/graphql/execution_error.rb +5 -1
  12. data/lib/graphql/query/context.rb +1 -1
  13. data/lib/graphql/schema/field.rb +3 -4
  14. data/lib/graphql/schema/list.rb +1 -1
  15. data/lib/graphql/schema/member/has_fields.rb +5 -1
  16. data/lib/graphql/schema/non_null.rb +1 -1
  17. data/lib/graphql/schema/resolver.rb +18 -3
  18. data/lib/graphql/schema/subscription.rb +0 -2
  19. data/lib/graphql/schema/visibility/profile.rb +68 -49
  20. data/lib/graphql/schema/wrapper.rb +7 -1
  21. data/lib/graphql/static_validation/base_visitor.rb +90 -66
  22. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  23. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +18 -6
  24. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +5 -2
  25. data/lib/graphql/static_validation/rules/directives_are_defined.rb +5 -2
  26. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +4 -3
  27. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +12 -2
  28. data/lib/graphql/static_validation/rules/fields_will_merge.rb +322 -256
  29. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +4 -4
  30. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  31. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +10 -7
  32. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +27 -7
  33. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +12 -9
  34. data/lib/graphql/static_validation/validation_context.rb +1 -1
  35. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +25 -1
  36. data/lib/graphql/subscriptions/event.rb +1 -0
  37. data/lib/graphql/subscriptions.rb +20 -0
  38. data/lib/graphql/tracing/perfetto_trace.rb +2 -2
  39. data/lib/graphql/unauthorized_error.rb +4 -0
  40. data/lib/graphql/version.rb +1 -1
  41. metadata +3 -3
@@ -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
@@ -17,10 +17,34 @@ module GraphQL
17
17
  end
18
18
  end
19
19
 
20
+ def resolve_next(context:, objects:, arguments:)
21
+ has_override_implementation = @field.execution_next_mode != :direct_send
22
+
23
+ if !has_override_implementation
24
+ if context.query.subscription_update?
25
+ objects
26
+ else
27
+ objects.map { |o| context.skip }
28
+ end
29
+ else
30
+ yield(objects, arguments)
31
+ end
32
+ end
33
+
20
34
  def after_resolve(value:, context:, object:, arguments:, **rest)
35
+ self.class.write_subscription(@field, value, arguments, context)
36
+ end
37
+
38
+ def after_resolve_next(values:, context:, objects:, arguments:, **rest)
39
+ values.map do |value|
40
+ self.class.write_subscription(@field, value, arguments, context)
41
+ end
42
+ end
43
+
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,26 @@ 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
+
242
262
  private
243
263
 
244
264
  # 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
  ]
@@ -29,5 +29,9 @@ module GraphQL
29
29
  end
30
30
 
31
31
  attr_accessor :path, :ast_nodes
32
+
33
+ def assign_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.23"
4
4
  end
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.23
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-03 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: base64
@@ -831,7 +831,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
831
831
  - !ruby/object:Gem::Version
832
832
  version: '0'
833
833
  requirements: []
834
- rubygems_version: 4.0.8
834
+ rubygems_version: 4.0.6
835
835
  specification_version: 4
836
836
  summary: A GraphQL language and runtime for Ruby
837
837
  test_files: []