graphql 2.5.9 → 2.5.26

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 (127) 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/analysis.rb +20 -13
  5. data/lib/graphql/dashboard/application_controller.rb +41 -0
  6. data/lib/graphql/dashboard/landings_controller.rb +9 -0
  7. data/lib/graphql/dashboard/statics_controller.rb +31 -0
  8. data/lib/graphql/dashboard/subscriptions.rb +2 -1
  9. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +1 -0
  10. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +2 -2
  11. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +1 -1
  12. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +1 -1
  13. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +1 -1
  14. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +1 -1
  15. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +1 -1
  16. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +7 -7
  17. data/lib/graphql/dashboard.rb +11 -73
  18. data/lib/graphql/dataloader/active_record_association_source.rb +14 -2
  19. data/lib/graphql/dataloader/async_dataloader.rb +22 -11
  20. data/lib/graphql/dataloader/null_dataloader.rb +54 -9
  21. data/lib/graphql/dataloader.rb +75 -23
  22. data/lib/graphql/date_encoding_error.rb +1 -1
  23. data/lib/graphql/execution/field_resolve_step.rb +631 -0
  24. data/lib/graphql/execution/finalize.rb +217 -0
  25. data/lib/graphql/execution/input_values.rb +261 -0
  26. data/lib/graphql/execution/interpreter/handles_raw_value.rb +6 -0
  27. data/lib/graphql/execution/interpreter/resolve.rb +10 -16
  28. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +13 -0
  29. data/lib/graphql/execution/interpreter/runtime.rb +28 -33
  30. data/lib/graphql/execution/interpreter.rb +8 -22
  31. data/lib/graphql/execution/lazy.rb +1 -1
  32. data/lib/graphql/execution/load_argument_step.rb +64 -0
  33. data/lib/graphql/execution/multiplex.rb +1 -1
  34. data/lib/graphql/execution/next.rb +90 -0
  35. data/lib/graphql/execution/prepare_object_step.rb +128 -0
  36. data/lib/graphql/execution/runner.rb +410 -0
  37. data/lib/graphql/execution/selections_step.rb +91 -0
  38. data/lib/graphql/execution.rb +8 -4
  39. data/lib/graphql/execution_error.rb +17 -10
  40. data/lib/graphql/introspection/directive_type.rb +7 -3
  41. data/lib/graphql/introspection/dynamic_fields.rb +5 -1
  42. data/lib/graphql/introspection/entry_points.rb +11 -3
  43. data/lib/graphql/introspection/enum_value_type.rb +5 -5
  44. data/lib/graphql/introspection/field_type.rb +13 -5
  45. data/lib/graphql/introspection/input_value_type.rb +21 -13
  46. data/lib/graphql/introspection/type_type.rb +64 -28
  47. data/lib/graphql/invalid_null_error.rb +11 -5
  48. data/lib/graphql/language/document_from_schema_definition.rb +2 -1
  49. data/lib/graphql/language/lexer.rb +20 -9
  50. data/lib/graphql/language/nodes.rb +5 -1
  51. data/lib/graphql/language/parser.rb +1 -0
  52. data/lib/graphql/language.rb +21 -12
  53. data/lib/graphql/pagination/connection.rb +2 -0
  54. data/lib/graphql/pagination/connections.rb +32 -0
  55. data/lib/graphql/query/context.rb +11 -4
  56. data/lib/graphql/query/null_context.rb +9 -3
  57. data/lib/graphql/query/partial.rb +18 -3
  58. data/lib/graphql/query.rb +10 -1
  59. data/lib/graphql/runtime_error.rb +6 -0
  60. data/lib/graphql/schema/addition.rb +3 -1
  61. data/lib/graphql/schema/argument.rb +17 -0
  62. data/lib/graphql/schema/build_from_definition.rb +15 -2
  63. data/lib/graphql/schema/directive.rb +45 -13
  64. data/lib/graphql/schema/field/connection_extension.rb +4 -37
  65. data/lib/graphql/schema/field/scope_extension.rb +18 -13
  66. data/lib/graphql/schema/field.rb +87 -48
  67. data/lib/graphql/schema/field_extension.rb +11 -8
  68. data/lib/graphql/schema/interface.rb +26 -0
  69. data/lib/graphql/schema/list.rb +5 -1
  70. data/lib/graphql/schema/member/base_dsl_methods.rb +1 -11
  71. data/lib/graphql/schema/member/has_arguments.rb +43 -14
  72. data/lib/graphql/schema/member/has_authorization.rb +35 -0
  73. data/lib/graphql/schema/member/has_dataloader.rb +37 -0
  74. data/lib/graphql/schema/member/has_fields.rb +86 -5
  75. data/lib/graphql/schema/member/has_interfaces.rb +2 -2
  76. data/lib/graphql/schema/member/type_system_helpers.rb +16 -2
  77. data/lib/graphql/schema/member.rb +5 -0
  78. data/lib/graphql/schema/non_null.rb +1 -1
  79. data/lib/graphql/schema/object.rb +1 -0
  80. data/lib/graphql/schema/ractor_shareable.rb +79 -0
  81. data/lib/graphql/schema/resolver.rb +60 -1
  82. data/lib/graphql/schema/subscription.rb +0 -2
  83. data/lib/graphql/schema/validator/required_validator.rb +45 -5
  84. data/lib/graphql/schema/visibility/migration.rb +2 -2
  85. data/lib/graphql/schema/visibility/profile.rb +140 -56
  86. data/lib/graphql/schema/visibility.rb +31 -18
  87. data/lib/graphql/schema/wrapper.rb +7 -1
  88. data/lib/graphql/schema.rb +108 -32
  89. data/lib/graphql/static_validation/all_rules.rb +1 -1
  90. data/lib/graphql/static_validation/base_visitor.rb +90 -66
  91. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  92. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +18 -6
  93. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +5 -2
  94. data/lib/graphql/static_validation/rules/directives_are_defined.rb +5 -2
  95. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +4 -3
  96. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +14 -4
  97. data/lib/graphql/static_validation/rules/fields_will_merge.rb +322 -256
  98. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +4 -4
  99. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  100. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +10 -7
  101. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +27 -7
  102. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +6 -2
  103. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +12 -9
  104. data/lib/graphql/static_validation/validation_context.rb +1 -1
  105. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -0
  106. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +34 -10
  107. data/lib/graphql/subscriptions/event.rb +1 -0
  108. data/lib/graphql/subscriptions.rb +36 -1
  109. data/lib/graphql/testing/helpers.rb +12 -9
  110. data/lib/graphql/testing/mock_action_cable.rb +111 -0
  111. data/lib/graphql/testing.rb +1 -0
  112. data/lib/graphql/tracing/detailed_trace/active_record_backend.rb +74 -0
  113. data/lib/graphql/tracing/detailed_trace.rb +70 -7
  114. data/lib/graphql/tracing/null_trace.rb +1 -1
  115. data/lib/graphql/tracing/perfetto_trace.rb +209 -79
  116. data/lib/graphql/tracing/sentry_trace.rb +3 -1
  117. data/lib/graphql/tracing/trace.rb +6 -0
  118. data/lib/graphql/type_kinds.rb +1 -0
  119. data/lib/graphql/types/relay/connection_behaviors.rb +8 -6
  120. data/lib/graphql/types/relay/edge_behaviors.rb +4 -3
  121. data/lib/graphql/types/relay/has_node_field.rb +13 -8
  122. data/lib/graphql/types/relay/has_nodes_field.rb +13 -8
  123. data/lib/graphql/types/relay/node_behaviors.rb +13 -2
  124. data/lib/graphql/unauthorized_error.rb +9 -1
  125. data/lib/graphql/version.rb +1 -1
  126. data/lib/graphql.rb +7 -3
  127. metadata +21 -3
@@ -7,6 +7,7 @@ require "graphql/schema/find_inherited_value"
7
7
  require "graphql/schema/finder"
8
8
  require "graphql/schema/introspection_system"
9
9
  require "graphql/schema/late_bound_type"
10
+ require "graphql/schema/ractor_shareable"
10
11
  require "graphql/schema/timeout"
11
12
  require "graphql/schema/type_expression"
12
13
  require "graphql/schema/unique_within_type"
@@ -148,10 +149,12 @@ module GraphQL
148
149
  end
149
150
 
150
151
  # @param new_mode [Symbol] If configured, this will be used when `context: { trace_mode: ... }` isn't set.
151
- def default_trace_mode(new_mode = nil)
152
- if new_mode
152
+ def default_trace_mode(new_mode = NOT_CONFIGURED)
153
+ if !NOT_CONFIGURED.equal?(new_mode)
153
154
  @default_trace_mode = new_mode
154
- elsif defined?(@default_trace_mode)
155
+ elsif defined?(@default_trace_mode) &&
156
+ !@default_trace_mode.nil? # This `nil?` check seems necessary because of
157
+ # Ractors silently initializing @default_trace_mode somehow
155
158
  @default_trace_mode
156
159
  elsif superclass.respond_to?(:default_trace_mode)
157
160
  superclass.default_trace_mode
@@ -327,10 +330,16 @@ module GraphQL
327
330
  find_inherited_value(:plugins, EMPTY_ARRAY) + own_plugins
328
331
  end
329
332
 
333
+ attr_writer :null_context
334
+
335
+ def null_context
336
+ @null_context || GraphQL::Query::NullContext.instance
337
+ end
338
+
330
339
  # Build a map of `{ name => type }` and return it
331
340
  # @return [Hash<String => Class>] A dictionary of type classes by their GraphQL name
332
341
  # @see get_type Which is more efficient for finding _one type_ by name, because it doesn't merge hashes.
333
- def types(context = GraphQL::Query::NullContext.instance)
342
+ def types(context = null_context)
334
343
  if use_visibility_profile?
335
344
  types = Visibility::Profile.from_context(context, self)
336
345
  return types.all_types_h
@@ -363,9 +372,10 @@ module GraphQL
363
372
  # @param context [GraphQL::Query::Context] Used for filtering definitions at query-time
364
373
  # @param use_visibility_profile Private, for migration to {Schema::Visibility}
365
374
  # @return [Module, nil] A type, or nil if there's no type called `type_name`
366
- 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?)
367
376
  if use_visibility_profile
368
- return Visibility::Profile.from_context(context, self).type(type_name)
377
+ profile = Visibility::Profile.from_context(context, self)
378
+ return profile.type(type_name)
369
379
  end
370
380
  local_entry = own_types[type_name]
371
381
  type_defn = case local_entry
@@ -613,7 +623,7 @@ module GraphQL
613
623
  # @param use_visibility_profile Private, for migration to {Schema::Visibility}
614
624
  # @return [Hash<String, Module>] All possible types, if no `type` is given.
615
625
  # @return [Array<Module>] Possible types for `type`, if it's given.
616
- 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?)
617
627
  if use_visibility_profile
618
628
  if type
619
629
  return Visibility::Profile.from_context(context, self).possible_types(type)
@@ -697,7 +707,21 @@ module GraphQL
697
707
  GraphQL::Schema::TypeExpression.build_type(context.query.types, ast_node)
698
708
  end
699
709
 
700
- def get_field(type_or_name, field_name, context = GraphQL::Query::NullContext.instance)
710
+ def get_field(type_or_name, field_name, context = null_context, use_visibility_profile = use_visibility_profile?)
711
+ if use_visibility_profile
712
+ profile = Visibility::Profile.from_context(context, self)
713
+ parent_type = case type_or_name
714
+ when String
715
+ profile.type(type_or_name)
716
+ when Module
717
+ type_or_name
718
+ when LateBoundType
719
+ profile.type(type_or_name.name)
720
+ else
721
+ raise GraphQL::InvariantError, "Unexpected field owner for #{field_name.inspect}: #{type_or_name.inspect} (#{type_or_name.class})"
722
+ end
723
+ return profile.field(parent_type, field_name)
724
+ end
701
725
  parent_type = case type_or_name
702
726
  when LateBoundType
703
727
  get_type(type_or_name.name, context)
@@ -720,7 +744,7 @@ module GraphQL
720
744
  end
721
745
  end
722
746
 
723
- def get_fields(type, context = GraphQL::Query::NullContext.instance)
747
+ def get_fields(type, context = null_context)
724
748
  type.fields(context)
725
749
  end
726
750
 
@@ -1105,20 +1129,21 @@ module GraphQL
1105
1129
  end
1106
1130
  end
1107
1131
 
1108
- NEW_HANDLER_HASH = ->(h, k) {
1109
- h[k] = {
1110
- class: k,
1111
- handler: nil,
1112
- subclass_handlers: Hash.new(&NEW_HANDLER_HASH),
1113
- }
1114
- }
1115
-
1116
1132
  def error_handlers
1117
- @error_handlers ||= {
1118
- class: nil,
1119
- handler: nil,
1120
- subclass_handlers: Hash.new(&NEW_HANDLER_HASH),
1121
- }
1133
+ @error_handlers ||= begin
1134
+ new_handler_hash = ->(h, k) {
1135
+ h[k] = {
1136
+ class: k,
1137
+ handler: nil,
1138
+ subclass_handlers: Hash.new(&new_handler_hash),
1139
+ }
1140
+ }
1141
+ {
1142
+ class: nil,
1143
+ handler: nil,
1144
+ subclass_handlers: Hash.new(&new_handler_hash),
1145
+ }
1146
+ end
1122
1147
  end
1123
1148
 
1124
1149
  # @api private
@@ -1209,6 +1234,7 @@ module GraphQL
1209
1234
  vis = self.visibility
1210
1235
  child_class.visibility = vis.dup_for(child_class)
1211
1236
  end
1237
+ child_class.null_context = Query::NullContext.new(schema: child_class)
1212
1238
  super
1213
1239
  end
1214
1240
 
@@ -1310,10 +1336,11 @@ module GraphQL
1310
1336
  def type_error(type_error, context)
1311
1337
  case type_error
1312
1338
  when GraphQL::InvalidNullError
1313
- execution_error = GraphQL::ExecutionError.new(type_error.message, ast_node: type_error.ast_node)
1314
- 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]
1315
1341
 
1316
1342
  context.errors << execution_error
1343
+ execution_error
1317
1344
  when GraphQL::UnresolvedTypeError, GraphQL::StringEncodingError, GraphQL::IntegerEncodingError
1318
1345
  raise type_error
1319
1346
  when GraphQL::IntegerDecodingError
@@ -1335,6 +1362,16 @@ module GraphQL
1335
1362
  lazy_methods.set(lazy_class, value_method)
1336
1363
  end
1337
1364
 
1365
+ def resolves_lazies?
1366
+ lazy_method_count = 0
1367
+ lazy_methods.each do |k, v|
1368
+ if !v.nil?
1369
+ lazy_method_count += 1
1370
+ end
1371
+ end
1372
+ lazy_method_count > 2
1373
+ end
1374
+
1338
1375
  def instrument(instrument_step, instrumenter, options = {})
1339
1376
  warn <<~WARN
1340
1377
  Schema.instrument is deprecated, use `trace_with` instead: https://graphql-ruby.org/queries/tracing.html"
@@ -1405,7 +1442,16 @@ module GraphQL
1405
1442
  end
1406
1443
 
1407
1444
  def tracers
1408
- find_inherited_value(:tracers, EMPTY_ARRAY) + own_tracers
1445
+ inherited = find_inherited_value(:tracers, EMPTY_ARRAY)
1446
+ if inherited.length > 0
1447
+ if own_tracers.length > 0
1448
+ inherited + own_tracers
1449
+ else
1450
+ inherited
1451
+ end
1452
+ else
1453
+ own_tracers
1454
+ end
1409
1455
  end
1410
1456
 
1411
1457
  # Mix `trace_mod` into this schema's `Trace` class so that its methods will be called at runtime.
@@ -1515,7 +1561,8 @@ module GraphQL
1515
1561
  end
1516
1562
 
1517
1563
  def query_analyzers
1518
- find_inherited_value(:query_analyzers, EMPTY_ARRAY) + own_query_analyzers
1564
+ inherited_qa = find_inherited_value(:query_analyzers, EMPTY_ARRAY)
1565
+ inherited_qa.empty? ? own_query_analyzers : (inherited_qa + own_query_analyzers)
1519
1566
  end
1520
1567
 
1521
1568
  # @param new_analyzer [Class<GraphQL::Analysis::Analyzer>] An analyzer to run on multiplexes to this schema
@@ -1689,18 +1736,38 @@ module GraphQL
1689
1736
  # If you need to support previous, non-spec behavior which allowed selecting union fields
1690
1737
  # but *not* selecting any fields on that union, set this to `true` to continue allowing that behavior.
1691
1738
  #
1692
- # If this is `true`, then {.legacy_invalid_empty_selections_on_union} will be called with {Query} objects
1739
+ # If this is `true`, then {.legacy_invalid_empty_selections_on_union_with_type} will be called with {Query} objects
1693
1740
  # with that kind of selections. You must implement that method
1694
1741
  # @param new_value [Boolean]
1695
1742
  # @return [true, false, nil]
1696
1743
  def allow_legacy_invalid_empty_selections_on_union(new_value = NOT_CONFIGURED)
1697
1744
  if NOT_CONFIGURED.equal?(new_value)
1698
- @allow_legacy_invalid_empty_selections_on_union
1745
+ if defined?(@allow_legacy_invalid_empty_selections_on_union)
1746
+ @allow_legacy_invalid_empty_selections_on_union
1747
+ else
1748
+ find_inherited_value(:allow_legacy_invalid_empty_selections_on_union)
1749
+ end
1699
1750
  else
1700
1751
  @allow_legacy_invalid_empty_selections_on_union = new_value
1701
1752
  end
1702
1753
  end
1703
1754
 
1755
+ # This method is called during validation when a previously-allowed, but non-spec
1756
+ # query is encountered where a union field has no child selections on it.
1757
+ #
1758
+ # If `legacy_invalid_empty_selections_on_union_with_type` is overridden, this method will not be called.
1759
+ #
1760
+ # You should implement this method or `legacy_invalid_empty_selections_on_union_with_type`
1761
+ # to log the violation so that you can contact clients and notify them about changing their queries.
1762
+ # Then return a suitable value to tell GraphQL-Ruby how to continue.
1763
+ # @param query [GraphQL::Query]
1764
+ # @return [:return_validation_error] Let GraphQL-Ruby return the (new) normal validation error for this query
1765
+ # @return [String] A validation error to return for this query
1766
+ # @return [nil] Don't send the client an error, continue the legacy behavior (allow this query to execute)
1767
+ def legacy_invalid_empty_selections_on_union(query)
1768
+ 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"
1769
+ end
1770
+
1704
1771
  # This method is called during validation when a previously-allowed, but non-spec
1705
1772
  # query is encountered where a union field has no child selections on it.
1706
1773
  #
@@ -1708,11 +1775,12 @@ module GraphQL
1708
1775
  # and notify them about changing their queries. Then return a suitable value to
1709
1776
  # tell GraphQL-Ruby how to continue.
1710
1777
  # @param query [GraphQL::Query]
1778
+ # @param type [Module] A GraphQL type definition
1711
1779
  # @return [:return_validation_error] Let GraphQL-Ruby return the (new) normal validation error for this query
1712
1780
  # @return [String] A validation error to return for this query
1713
1781
  # @return [nil] Don't send the client an error, continue the legacy behavior (allow this query to execute)
1714
- def legacy_invalid_empty_selections_on_union(query)
1715
- raise "Implement `def self.legacy_invalid_empty_selections_on_union(query)` to handle this scenario"
1782
+ def legacy_invalid_empty_selections_on_union_with_type(query, type)
1783
+ legacy_invalid_empty_selections_on_union(query)
1716
1784
  end
1717
1785
 
1718
1786
  # This setting controls how GraphQL-Ruby handles overlapping selections on scalar types when the types
@@ -1726,7 +1794,11 @@ module GraphQL
1726
1794
  # @return [true, false, nil]
1727
1795
  def allow_legacy_invalid_return_type_conflicts(new_value = NOT_CONFIGURED)
1728
1796
  if NOT_CONFIGURED.equal?(new_value)
1729
- @allow_legacy_invalid_return_type_conflicts
1797
+ if defined?(@allow_legacy_invalid_return_type_conflicts)
1798
+ @allow_legacy_invalid_return_type_conflicts
1799
+ else
1800
+ find_inherited_value(:allow_legacy_invalid_return_type_conflicts)
1801
+ end
1730
1802
  else
1731
1803
  @allow_legacy_invalid_return_type_conflicts = new_value
1732
1804
  end
@@ -1774,7 +1846,11 @@ module GraphQL
1774
1846
  # complexity_cost_calculation_mode(:compare)
1775
1847
  def complexity_cost_calculation_mode(new_mode = NOT_CONFIGURED)
1776
1848
  if NOT_CONFIGURED.equal?(new_mode)
1777
- @complexity_cost_calculation_mode
1849
+ if defined?(@complexity_cost_calculation_mode)
1850
+ @complexity_cost_calculation_mode
1851
+ else
1852
+ find_inherited_value(:complexity_cost_calculation_mode)
1853
+ end
1778
1854
  else
1779
1855
  @complexity_cost_calculation_mode = new_mode
1780
1856
  end
@@ -37,6 +37,6 @@ module GraphQL
37
37
  GraphQL::StaticValidation::SubscriptionRootExistsAndSingleSubscriptionSelection,
38
38
  GraphQL::StaticValidation::InputObjectNamesAreUnique,
39
39
  GraphQL::StaticValidation::OneOfInputObjectsAreValid,
40
- ]
40
+ ].freeze
41
41
  end
42
42
  end
@@ -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,