graphql 2.5.11 → 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 (105) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/detailed_trace_generator.rb +77 -0
  3. data/lib/generators/graphql/templates/create_graphql_detailed_traces.erb +10 -0
  4. data/lib/graphql/dashboard/application_controller.rb +41 -0
  5. data/lib/graphql/dashboard/landings_controller.rb +9 -0
  6. data/lib/graphql/dashboard/statics_controller.rb +31 -0
  7. data/lib/graphql/dashboard/subscriptions.rb +2 -1
  8. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +1 -0
  9. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +2 -2
  10. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +1 -1
  11. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +1 -1
  12. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +1 -1
  13. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +1 -1
  14. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +1 -1
  15. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +7 -7
  16. data/lib/graphql/dashboard.rb +11 -73
  17. data/lib/graphql/dataloader/async_dataloader.rb +22 -11
  18. data/lib/graphql/dataloader/null_dataloader.rb +48 -10
  19. data/lib/graphql/dataloader.rb +75 -23
  20. data/lib/graphql/date_encoding_error.rb +1 -1
  21. data/lib/graphql/execution/interpreter/resolve.rb +7 -13
  22. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +13 -0
  23. data/lib/graphql/execution/interpreter/runtime.rb +24 -18
  24. data/lib/graphql/execution/interpreter.rb +8 -22
  25. data/lib/graphql/execution/lazy.rb +1 -1
  26. data/lib/graphql/execution/multiplex.rb +1 -1
  27. data/lib/graphql/execution/next/field_resolve_step.rb +743 -0
  28. data/lib/graphql/execution/next/load_argument_step.rb +64 -0
  29. data/lib/graphql/execution/next/prepare_object_step.rb +129 -0
  30. data/lib/graphql/execution/next/runner.rb +411 -0
  31. data/lib/graphql/execution/next/selections_step.rb +37 -0
  32. data/lib/graphql/execution/next.rb +72 -0
  33. data/lib/graphql/execution.rb +8 -4
  34. data/lib/graphql/execution_error.rb +17 -10
  35. data/lib/graphql/introspection/directive_type.rb +7 -3
  36. data/lib/graphql/introspection/dynamic_fields.rb +5 -1
  37. data/lib/graphql/introspection/entry_points.rb +11 -3
  38. data/lib/graphql/introspection/enum_value_type.rb +5 -5
  39. data/lib/graphql/introspection/field_type.rb +13 -5
  40. data/lib/graphql/introspection/input_value_type.rb +21 -13
  41. data/lib/graphql/introspection/type_type.rb +64 -28
  42. data/lib/graphql/invalid_null_error.rb +11 -5
  43. data/lib/graphql/language/document_from_schema_definition.rb +2 -1
  44. data/lib/graphql/language.rb +21 -12
  45. data/lib/graphql/pagination/connection.rb +2 -0
  46. data/lib/graphql/pagination/connections.rb +32 -0
  47. data/lib/graphql/query/context.rb +4 -3
  48. data/lib/graphql/query/null_context.rb +9 -3
  49. data/lib/graphql/schema/argument.rb +12 -0
  50. data/lib/graphql/schema/build_from_definition.rb +10 -1
  51. data/lib/graphql/schema/directive.rb +22 -4
  52. data/lib/graphql/schema/field/connection_extension.rb +15 -35
  53. data/lib/graphql/schema/field/scope_extension.rb +22 -13
  54. data/lib/graphql/schema/field.rb +79 -48
  55. data/lib/graphql/schema/field_extension.rb +33 -0
  56. data/lib/graphql/schema/list.rb +1 -1
  57. data/lib/graphql/schema/member/base_dsl_methods.rb +1 -1
  58. data/lib/graphql/schema/member/has_arguments.rb +43 -14
  59. data/lib/graphql/schema/member/has_authorization.rb +35 -0
  60. data/lib/graphql/schema/member/has_dataloader.rb +37 -0
  61. data/lib/graphql/schema/member/has_fields.rb +86 -5
  62. data/lib/graphql/schema/member.rb +5 -0
  63. data/lib/graphql/schema/non_null.rb +1 -1
  64. data/lib/graphql/schema/object.rb +1 -0
  65. data/lib/graphql/schema/resolver.rb +60 -1
  66. data/lib/graphql/schema/subscription.rb +0 -2
  67. data/lib/graphql/schema/validator/required_validator.rb +33 -2
  68. data/lib/graphql/schema/visibility/profile.rb +68 -49
  69. data/lib/graphql/schema/visibility.rb +3 -3
  70. data/lib/graphql/schema/wrapper.rb +7 -1
  71. data/lib/graphql/schema.rb +53 -10
  72. data/lib/graphql/static_validation/base_visitor.rb +90 -66
  73. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  74. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +18 -6
  75. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +5 -2
  76. data/lib/graphql/static_validation/rules/directives_are_defined.rb +5 -2
  77. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +4 -3
  78. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +14 -4
  79. data/lib/graphql/static_validation/rules/fields_will_merge.rb +322 -256
  80. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +4 -4
  81. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  82. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +10 -7
  83. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +27 -7
  84. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +12 -9
  85. data/lib/graphql/static_validation/validation_context.rb +1 -1
  86. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -0
  87. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +25 -1
  88. data/lib/graphql/subscriptions/event.rb +1 -0
  89. data/lib/graphql/subscriptions.rb +21 -1
  90. data/lib/graphql/testing/helpers.rb +12 -9
  91. data/lib/graphql/testing/mock_action_cable.rb +111 -0
  92. data/lib/graphql/testing.rb +1 -0
  93. data/lib/graphql/tracing/detailed_trace/active_record_backend.rb +74 -0
  94. data/lib/graphql/tracing/detailed_trace.rb +70 -7
  95. data/lib/graphql/tracing/perfetto_trace.rb +209 -79
  96. data/lib/graphql/tracing/sentry_trace.rb +3 -1
  97. data/lib/graphql/types/relay/connection_behaviors.rb +8 -6
  98. data/lib/graphql/types/relay/edge_behaviors.rb +4 -3
  99. data/lib/graphql/types/relay/has_node_field.rb +13 -8
  100. data/lib/graphql/types/relay/has_nodes_field.rb +13 -8
  101. data/lib/graphql/types/relay/node_behaviors.rb +13 -2
  102. data/lib/graphql/unauthorized_error.rb +9 -1
  103. data/lib/graphql/version.rb +1 -1
  104. data/lib/graphql.rb +8 -2
  105. metadata +17 -3
@@ -330,10 +330,16 @@ module GraphQL
330
330
  find_inherited_value(:plugins, EMPTY_ARRAY) + own_plugins
331
331
  end
332
332
 
333
+ attr_writer :null_context
334
+
335
+ def null_context
336
+ @null_context || GraphQL::Query::NullContext.instance
337
+ end
338
+
333
339
  # Build a map of `{ name => type }` and return it
334
340
  # @return [Hash<String => Class>] A dictionary of type classes by their GraphQL name
335
341
  # @see get_type Which is more efficient for finding _one type_ by name, because it doesn't merge hashes.
336
- def types(context = GraphQL::Query::NullContext.instance)
342
+ def types(context = null_context)
337
343
  if use_visibility_profile?
338
344
  types = Visibility::Profile.from_context(context, self)
339
345
  return types.all_types_h
@@ -366,7 +372,7 @@ module GraphQL
366
372
  # @param context [GraphQL::Query::Context] Used for filtering definitions at query-time
367
373
  # @param use_visibility_profile Private, for migration to {Schema::Visibility}
368
374
  # @return [Module, nil] A type, or nil if there's no type called `type_name`
369
- def get_type(type_name, context = GraphQL::Query::NullContext.instance, use_visibility_profile = use_visibility_profile?)
375
+ def get_type(type_name, context = null_context, use_visibility_profile = use_visibility_profile?)
370
376
  if use_visibility_profile
371
377
  profile = Visibility::Profile.from_context(context, self)
372
378
  return profile.type(type_name)
@@ -617,7 +623,7 @@ module GraphQL
617
623
  # @param use_visibility_profile Private, for migration to {Schema::Visibility}
618
624
  # @return [Hash<String, Module>] All possible types, if no `type` is given.
619
625
  # @return [Array<Module>] Possible types for `type`, if it's given.
620
- def possible_types(type = nil, context = GraphQL::Query::NullContext.instance, use_visibility_profile = use_visibility_profile?)
626
+ def possible_types(type = nil, context = null_context, use_visibility_profile = use_visibility_profile?)
621
627
  if use_visibility_profile
622
628
  if type
623
629
  return Visibility::Profile.from_context(context, self).possible_types(type)
@@ -701,7 +707,7 @@ module GraphQL
701
707
  GraphQL::Schema::TypeExpression.build_type(context.query.types, ast_node)
702
708
  end
703
709
 
704
- def get_field(type_or_name, field_name, context = GraphQL::Query::NullContext.instance, use_visibility_profile = use_visibility_profile?)
710
+ def get_field(type_or_name, field_name, context = null_context, use_visibility_profile = use_visibility_profile?)
705
711
  if use_visibility_profile
706
712
  profile = Visibility::Profile.from_context(context, self)
707
713
  parent_type = case type_or_name
@@ -738,7 +744,7 @@ module GraphQL
738
744
  end
739
745
  end
740
746
 
741
- def get_fields(type, context = GraphQL::Query::NullContext.instance)
747
+ def get_fields(type, context = null_context)
742
748
  type.fields(context)
743
749
  end
744
750
 
@@ -1228,6 +1234,7 @@ module GraphQL
1228
1234
  vis = self.visibility
1229
1235
  child_class.visibility = vis.dup_for(child_class)
1230
1236
  end
1237
+ child_class.null_context = Query::NullContext.new(schema: child_class)
1231
1238
  super
1232
1239
  end
1233
1240
 
@@ -1329,10 +1336,11 @@ module GraphQL
1329
1336
  def type_error(type_error, context)
1330
1337
  case type_error
1331
1338
  when GraphQL::InvalidNullError
1332
- execution_error = GraphQL::ExecutionError.new(type_error.message, ast_node: type_error.ast_node)
1333
- execution_error.path = context[:current_path]
1339
+ execution_error = GraphQL::ExecutionError.new(type_error.message, ast_nodes: type_error.ast_nodes)
1340
+ execution_error.path = type_error.path || context[:current_path]
1334
1341
 
1335
1342
  context.errors << execution_error
1343
+ execution_error
1336
1344
  when GraphQL::UnresolvedTypeError, GraphQL::StringEncodingError, GraphQL::IntegerEncodingError
1337
1345
  raise type_error
1338
1346
  when GraphQL::IntegerDecodingError
@@ -1354,6 +1362,24 @@ module GraphQL
1354
1362
  lazy_methods.set(lazy_class, value_method)
1355
1363
  end
1356
1364
 
1365
+ def uses_raw_value?
1366
+ !!@uses_raw_value
1367
+ end
1368
+
1369
+ def uses_raw_value(new_val)
1370
+ @uses_raw_value = new_val
1371
+ end
1372
+
1373
+ def resolves_lazies?
1374
+ lazy_method_count = 0
1375
+ lazy_methods.each do |k, v|
1376
+ if !v.nil?
1377
+ lazy_method_count += 1
1378
+ end
1379
+ end
1380
+ lazy_method_count > 2
1381
+ end
1382
+
1357
1383
  def instrument(instrument_step, instrumenter, options = {})
1358
1384
  warn <<~WARN
1359
1385
  Schema.instrument is deprecated, use `trace_with` instead: https://graphql-ruby.org/queries/tracing.html"
@@ -1708,7 +1734,7 @@ module GraphQL
1708
1734
  # If you need to support previous, non-spec behavior which allowed selecting union fields
1709
1735
  # but *not* selecting any fields on that union, set this to `true` to continue allowing that behavior.
1710
1736
  #
1711
- # If this is `true`, then {.legacy_invalid_empty_selections_on_union} will be called with {Query} objects
1737
+ # If this is `true`, then {.legacy_invalid_empty_selections_on_union_with_type} will be called with {Query} objects
1712
1738
  # with that kind of selections. You must implement that method
1713
1739
  # @param new_value [Boolean]
1714
1740
  # @return [true, false, nil]
@@ -1724,6 +1750,22 @@ module GraphQL
1724
1750
  end
1725
1751
  end
1726
1752
 
1753
+ # This method is called during validation when a previously-allowed, but non-spec
1754
+ # query is encountered where a union field has no child selections on it.
1755
+ #
1756
+ # If `legacy_invalid_empty_selections_on_union_with_type` is overridden, this method will not be called.
1757
+ #
1758
+ # You should implement this method or `legacy_invalid_empty_selections_on_union_with_type`
1759
+ # to log the violation so that you can contact clients and notify them about changing their queries.
1760
+ # Then return a suitable value to tell GraphQL-Ruby how to continue.
1761
+ # @param query [GraphQL::Query]
1762
+ # @return [:return_validation_error] Let GraphQL-Ruby return the (new) normal validation error for this query
1763
+ # @return [String] A validation error to return for this query
1764
+ # @return [nil] Don't send the client an error, continue the legacy behavior (allow this query to execute)
1765
+ def legacy_invalid_empty_selections_on_union(query)
1766
+ raise "Implement `def self.legacy_invalid_empty_selections_on_union_with_type(query, type)` or `def self.legacy_invalid_empty_selections_on_union(query)` to handle this scenario"
1767
+ end
1768
+
1727
1769
  # This method is called during validation when a previously-allowed, but non-spec
1728
1770
  # query is encountered where a union field has no child selections on it.
1729
1771
  #
@@ -1731,11 +1773,12 @@ module GraphQL
1731
1773
  # and notify them about changing their queries. Then return a suitable value to
1732
1774
  # tell GraphQL-Ruby how to continue.
1733
1775
  # @param query [GraphQL::Query]
1776
+ # @param type [Module] A GraphQL type definition
1734
1777
  # @return [:return_validation_error] Let GraphQL-Ruby return the (new) normal validation error for this query
1735
1778
  # @return [String] A validation error to return for this query
1736
1779
  # @return [nil] Don't send the client an error, continue the legacy behavior (allow this query to execute)
1737
- def legacy_invalid_empty_selections_on_union(query)
1738
- raise "Implement `def self.legacy_invalid_empty_selections_on_union(query)` to handle this scenario"
1780
+ def legacy_invalid_empty_selections_on_union_with_type(query, type)
1781
+ legacy_invalid_empty_selections_on_union(query)
1739
1782
  end
1740
1783
 
1741
1784
  # This setting controls how GraphQL-Ruby handles overlapping selections on scalar types when the types
@@ -4,25 +4,26 @@ module GraphQL
4
4
  class BaseVisitor < GraphQL::Language::StaticVisitor
5
5
  def initialize(document, context)
6
6
  @path = []
7
- @object_types = []
8
- @directives = []
9
- @field_definitions = []
10
- @argument_definitions = []
11
- @directive_definitions = []
7
+ @path_depth = 0
8
+ @current_object_type = nil
9
+ @parent_object_type = nil
10
+ @current_field_definition = nil
11
+ @current_argument_definition = nil
12
+ @parent_argument_definition = nil
13
+ @current_directive_definition = nil
12
14
  @context = context
13
15
  @types = context.query.types
14
16
  @schema = context.schema
17
+ @inline_fragment_paths = {}
18
+ @field_unwrapped_types = {}.compare_by_identity
15
19
  super(document)
16
20
  end
17
21
 
18
22
  attr_reader :context
19
23
 
20
- # @return [Array<GraphQL::ObjectType>] Types whose scope we've entered
21
- attr_reader :object_types
22
-
23
24
  # @return [Array<String>] The nesting of the current position in the AST
24
25
  def path
25
- @path.dup
26
+ @path[0, @path_depth]
26
27
  end
27
28
 
28
29
  # Build a class to visit the AST and perform validation,
@@ -55,86 +56,125 @@ module GraphQL
55
56
  module ContextMethods
56
57
  def on_operation_definition(node, parent)
57
58
  object_type = @schema.root_type_for_operation(node.operation_type)
58
- push_type(object_type)
59
- @path.push("#{node.operation_type}#{node.name ? " #{node.name}" : ""}")
59
+ prev_parent_ot = @parent_object_type
60
+ @parent_object_type = @current_object_type
61
+ @current_object_type = object_type
62
+ @path[@path_depth] = "#{node.operation_type}#{node.name ? " #{node.name}" : ""}"
63
+ @path_depth += 1
60
64
  super
61
- @object_types.pop
62
- @path.pop
65
+ @current_object_type = @parent_object_type
66
+ @parent_object_type = prev_parent_ot
67
+ @path_depth -= 1
63
68
  end
64
69
 
65
70
  def on_fragment_definition(node, parent)
66
- on_fragment_with_type(node) do
67
- @path.push("fragment #{node.name}")
68
- super
71
+ object_type = if node.type
72
+ @types.type(node.type.name)
73
+ else
74
+ @current_object_type
69
75
  end
76
+ prev_parent_ot = @parent_object_type
77
+ @parent_object_type = @current_object_type
78
+ @current_object_type = object_type
79
+ @path[@path_depth] = "fragment #{node.name}"
80
+ @path_depth += 1
81
+ super
82
+ @current_object_type = @parent_object_type
83
+ @parent_object_type = prev_parent_ot
84
+ @path_depth -= 1
70
85
  end
71
86
 
87
+ INLINE_FRAGMENT_NO_TYPE = "..."
88
+
72
89
  def on_inline_fragment(node, parent)
73
- on_fragment_with_type(node) do
74
- @path.push("...#{node.type ? " on #{node.type.to_query_string}" : ""}")
75
- super
90
+ if node.type
91
+ object_type = @types.type(node.type.name)
92
+ @path[@path_depth] = @inline_fragment_paths[node.type.name] ||= -"... on #{node.type.to_query_string}"
93
+ @path_depth += 1
94
+ else
95
+ object_type = @current_object_type
96
+ @path[@path_depth] = INLINE_FRAGMENT_NO_TYPE
97
+ @path_depth += 1
76
98
  end
99
+ prev_parent_ot = @parent_object_type
100
+ @parent_object_type = @current_object_type
101
+ @current_object_type = object_type
102
+ super
103
+ @current_object_type = @parent_object_type
104
+ @parent_object_type = prev_parent_ot
105
+ @path_depth -= 1
77
106
  end
78
107
 
79
108
  def on_field(node, parent)
80
- parent_type = @object_types.last
109
+ parent_type = @current_object_type
81
110
  field_definition = @types.field(parent_type, node.name)
82
- @field_definitions.push(field_definition)
83
- if !field_definition.nil?
84
- next_object_type = field_definition.type.unwrap
85
- push_type(next_object_type)
111
+ prev_field_definition = @current_field_definition
112
+ @current_field_definition = field_definition
113
+ prev_parent_ot = @parent_object_type
114
+ @parent_object_type = @current_object_type
115
+ if field_definition
116
+ @current_object_type = @field_unwrapped_types[field_definition] ||= field_definition.type.unwrap
86
117
  else
87
- push_type(nil)
118
+ @current_object_type = nil
88
119
  end
89
- @path.push(node.alias || node.name)
120
+ @path[@path_depth] = node.alias || node.name
121
+ @path_depth += 1
90
122
  super
91
- @field_definitions.pop
92
- @object_types.pop
93
- @path.pop
123
+ @current_field_definition = prev_field_definition
124
+ @current_object_type = @parent_object_type
125
+ @parent_object_type = prev_parent_ot
126
+ @path_depth -= 1
94
127
  end
95
128
 
96
129
  def on_directive(node, parent)
97
130
  directive_defn = @context.schema_directives[node.name]
98
- @directive_definitions.push(directive_defn)
131
+ prev_directive_definition = @current_directive_definition
132
+ @current_directive_definition = directive_defn
99
133
  super
100
- @directive_definitions.pop
134
+ @current_directive_definition = prev_directive_definition
101
135
  end
102
136
 
103
137
  def on_argument(node, parent)
104
- argument_defn = if (arg = @argument_definitions.last)
138
+ argument_defn = if (arg = @current_argument_definition)
105
139
  arg_type = arg.type.unwrap
106
140
  if arg_type.kind.input_object?
107
141
  @types.argument(arg_type, node.name)
108
142
  else
109
143
  nil
110
144
  end
111
- elsif (directive_defn = @directive_definitions.last)
145
+ elsif (directive_defn = @current_directive_definition)
112
146
  @types.argument(directive_defn, node.name)
113
- elsif (field_defn = @field_definitions.last)
147
+ elsif (field_defn = @current_field_definition)
114
148
  @types.argument(field_defn, node.name)
115
149
  else
116
150
  nil
117
151
  end
118
152
 
119
- @argument_definitions.push(argument_defn)
120
- @path.push(node.name)
153
+ prev_parent = @parent_argument_definition
154
+ @parent_argument_definition = @current_argument_definition
155
+ @current_argument_definition = argument_defn
156
+ @path[@path_depth] = node.name
157
+ @path_depth += 1
121
158
  super
122
- @argument_definitions.pop
123
- @path.pop
159
+ @current_argument_definition = @parent_argument_definition
160
+ @parent_argument_definition = prev_parent
161
+ @path_depth -= 1
124
162
  end
125
163
 
126
164
  def on_fragment_spread(node, parent)
127
- @path.push("... #{node.name}")
165
+ @path[@path_depth] = "... #{node.name}"
166
+ @path_depth += 1
128
167
  super
129
- @path.pop
168
+ @path_depth -= 1
130
169
  end
131
170
 
132
171
  def on_input_object(node, parent)
133
- arg_defn = @argument_definitions.last
172
+ arg_defn = @current_argument_definition
134
173
  if arg_defn && arg_defn.type.list?
135
- @path.push(parent.children.index(node))
174
+ @path[@path_depth] = parent.children.index(node)
175
+ @path_depth += 1
136
176
  super
137
- @path.pop
177
+ @path_depth -= 1
138
178
  else
139
179
  super
140
180
  end
@@ -142,48 +182,32 @@ module GraphQL
142
182
 
143
183
  # @return [GraphQL::BaseType] The current object type
144
184
  def type_definition
145
- @object_types.last
185
+ @current_object_type
146
186
  end
147
187
 
148
188
  # @return [GraphQL::BaseType] The type which the current type came from
149
189
  def parent_type_definition
150
- @object_types[-2]
190
+ @parent_object_type
151
191
  end
152
192
 
153
193
  # @return [GraphQL::Field, nil] The most-recently-entered GraphQL::Field, if currently inside one
154
194
  def field_definition
155
- @field_definitions.last
195
+ @current_field_definition
156
196
  end
157
197
 
158
198
  # @return [GraphQL::Directive, nil] The most-recently-entered GraphQL::Directive, if currently inside one
159
199
  def directive_definition
160
- @directive_definitions.last
200
+ @current_directive_definition
161
201
  end
162
202
 
163
203
  # @return [GraphQL::Argument, nil] The most-recently-entered GraphQL::Argument, if currently inside one
164
204
  def argument_definition
165
- # Don't get the _last_ one because that's the current one.
166
- # Get the second-to-last one, which is the parent of the current one.
167
- @argument_definitions[-2]
205
+ # Return the parent argument definition (not the current one).
206
+ @parent_argument_definition
168
207
  end
169
208
 
170
209
  private
171
210
 
172
- def on_fragment_with_type(node)
173
- object_type = if node.type
174
- @types.type(node.type.name)
175
- else
176
- @object_types.last
177
- end
178
- push_type(object_type)
179
- yield(node)
180
- @object_types.pop
181
- @path.pop
182
- end
183
-
184
- def push_type(t)
185
- @object_types.push(t)
186
- end
187
211
  end
188
212
 
189
213
  private
@@ -192,7 +216,7 @@ module GraphQL
192
216
  if @context.too_many_errors?
193
217
  throw :too_many_validation_errors
194
218
  end
195
- error.path ||= (path || @path.dup)
219
+ error.path ||= (path || @path[0, @path_depth])
196
220
  context.errors << error
197
221
  end
198
222
 
@@ -12,7 +12,7 @@ module GraphQL
12
12
  return
13
13
  end
14
14
 
15
- if @context.schema.error_bubbling || context.errors.none? { |err| err.path.take(@path.size) == @path }
15
+ if @context.schema.error_bubbling || context.errors.none? { |err| err.path.take(@path_depth) == @path[0, @path_depth] }
16
16
  parent_defn = parent_definition(parent)
17
17
 
18
18
  if parent_defn && (arg_defn = @types.argument(parent_defn, node.name))
@@ -16,12 +16,24 @@ module GraphQL
16
16
 
17
17
  def validate_arguments(node)
18
18
  argument_defns = node.arguments
19
- if !argument_defns.empty?
20
- args_by_name = Hash.new { |h, k| h[k] = [] }
21
- argument_defns.each { |a| args_by_name[a.name] << a }
22
- args_by_name.each do |name, defns|
23
- if defns.size > 1
24
- add_error(GraphQL::StaticValidation::ArgumentNamesAreUniqueError.new("There can be only one argument named \"#{name}\"", nodes: defns, name: name))
19
+ if argument_defns.size > 1
20
+ seen = {}
21
+ argument_defns.each do |a|
22
+ name = a.name
23
+ if seen.key?(name)
24
+ prev = seen[name]
25
+ if prev.is_a?(Array)
26
+ prev << a
27
+ else
28
+ seen[name] = [prev, a]
29
+ end
30
+ else
31
+ seen[name] = a
32
+ end
33
+ end
34
+ seen.each do |name, val|
35
+ if val.is_a?(Array)
36
+ add_error(GraphQL::StaticValidation::ArgumentNamesAreUniqueError.new("There can be only one argument named \"#{name}\"", nodes: val, name: name))
25
37
  end
26
38
  end
27
39
  end
@@ -10,9 +10,12 @@ module GraphQL
10
10
  elsif parent_defn
11
11
  kind_of_node = node_type(parent)
12
12
  error_arg_name = parent_name(parent, parent_defn)
13
- arg_names = context.types.arguments(parent_defn).map(&:graphql_name)
13
+ suggestion = if @schema.did_you_mean
14
+ arg_names = context.types.arguments(parent_defn).map(&:graphql_name)
15
+ context.did_you_mean_suggestion(node.name, arg_names)
16
+ end
14
17
  add_error(GraphQL::StaticValidation::ArgumentsAreDefinedError.new(
15
- "#{kind_of_node} '#{error_arg_name}' doesn't accept argument '#{node.name}'#{context.did_you_mean_suggestion(node.name, arg_names)}",
18
+ "#{kind_of_node} '#{error_arg_name}' doesn't accept argument '#{node.name}'#{suggestion}",
16
19
  nodes: node,
17
20
  name: error_arg_name,
18
21
  type: kind_of_node,
@@ -10,9 +10,12 @@ module GraphQL
10
10
  if !@types.directive_exists?(node.name)
11
11
  @directives_are_defined_errors_by_name ||= {}
12
12
  error = @directives_are_defined_errors_by_name[node.name] ||= begin
13
- @directive_names ||= @types.directives.map(&:graphql_name)
13
+ suggestion = if @schema.did_you_mean
14
+ @directive_names ||= @types.directives.map(&:graphql_name)
15
+ context.did_you_mean_suggestion(node.name, @directive_names)
16
+ end
14
17
  err = GraphQL::StaticValidation::DirectivesAreDefinedError.new(
15
- "Directive @#{node.name} is not defined#{context.did_you_mean_suggestion(node.name, @directive_names)}",
18
+ "Directive @#{node.name} is not defined#{suggestion}",
16
19
  nodes: [],
17
20
  directive: node.name
18
21
  )
@@ -3,7 +3,7 @@ module GraphQL
3
3
  module StaticValidation
4
4
  module FieldsAreDefinedOnType
5
5
  def on_field(node, parent)
6
- parent_type = @object_types[-2]
6
+ parent_type = @parent_object_type
7
7
  field = context.query.types.field(parent_type, node.name)
8
8
 
9
9
  if field.nil?
@@ -14,8 +14,9 @@ module GraphQL
14
14
  node_name: parent_type.graphql_name
15
15
  ))
16
16
  else
17
- possible_fields = possible_fields(context, parent_type)
18
- suggestion = context.did_you_mean_suggestion(node.name, possible_fields)
17
+ suggestion = if @schema.did_you_mean
18
+ context.did_you_mean_suggestion(node.name, possible_fields(context, parent_type))
19
+ end
19
20
  message = "Field '#{node.name}' doesn't exist on type '#{parent_type.graphql_name}'#{suggestion}"
20
21
  add_error(GraphQL::StaticValidation::FieldsAreDefinedOnTypeError.new(
21
22
  message,
@@ -7,8 +7,7 @@ module GraphQL
7
7
  include GraphQL::StaticValidation::Error::ErrorHelper
8
8
 
9
9
  def on_field(node, parent)
10
- field_defn = field_definition
11
- if validate_field_selections(node, field_defn.type.unwrap)
10
+ if validate_field_selections(node, @current_object_type)
12
11
  super
13
12
  end
14
13
  end
@@ -23,6 +22,17 @@ module GraphQL
23
22
 
24
23
 
25
24
  def validate_field_selections(ast_node, resolved_type)
25
+ # Fast paths for the two most common cases:
26
+ # 1. Leaf type with no selections (scalars, enums) — most fields
27
+ # 2. Non-leaf type with selections (objects, interfaces)
28
+ if resolved_type
29
+ if ast_node.selections.empty?
30
+ return true if resolved_type.kind.leaf?
31
+ else
32
+ return true unless resolved_type.kind.leaf?
33
+ end
34
+ end
35
+
26
36
  msg = if resolved_type.nil?
27
37
  nil
28
38
  elsif resolved_type.kind.leaf?
@@ -49,7 +59,7 @@ module GraphQL
49
59
  if !resolved_type.kind.fields?
50
60
  case @schema.allow_legacy_invalid_empty_selections_on_union
51
61
  when true
52
- legacy_invalid_empty_selection_result = @schema.legacy_invalid_empty_selections_on_union(@context.query)
62
+ legacy_invalid_empty_selection_result = @schema.legacy_invalid_empty_selections_on_union_with_type(@context.query, resolved_type)
53
63
  case legacy_invalid_empty_selection_result
54
64
  when :return_validation_error
55
65
  # keep `return_validation_error = true`
@@ -61,7 +71,7 @@ module GraphQL
61
71
  return_validation_error = false
62
72
  legacy_invalid_empty_selection_result = nil
63
73
  else
64
- raise GraphQL::InvariantError, "Unexpected return value from legacy_invalid_empty_selections_on_union, must be `:return_validation_error`, String, or nil (got: #{legacy_invalid_empty_selection_result.inspect})"
74
+ raise GraphQL::InvariantError, "Unexpected return value from legacy_invalid_empty_selections_on_union_with_type, must be `:return_validation_error`, String, or nil (got: #{legacy_invalid_empty_selection_result.inspect})"
65
75
  end
66
76
  when false
67
77
  # pass -- error below