graphql 1.12.20 → 1.13.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (125) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/core.rb +3 -1
  3. data/lib/generators/graphql/install_generator.rb +9 -2
  4. data/lib/generators/graphql/mutation_generator.rb +1 -1
  5. data/lib/generators/graphql/object_generator.rb +2 -1
  6. data/lib/generators/graphql/templates/schema.erb +2 -2
  7. data/lib/generators/graphql/type_generator.rb +0 -1
  8. data/lib/graphql/analysis/ast/field_usage.rb +2 -2
  9. data/lib/graphql/analysis/ast/query_complexity.rb +10 -14
  10. data/lib/graphql/analysis/ast/visitor.rb +4 -4
  11. data/lib/graphql/backtrace/table.rb +1 -1
  12. data/lib/graphql/base_type.rb +4 -2
  13. data/lib/graphql/boolean_type.rb +1 -1
  14. data/lib/graphql/dataloader.rb +55 -22
  15. data/lib/graphql/directive/deprecated_directive.rb +1 -1
  16. data/lib/graphql/directive/include_directive.rb +1 -1
  17. data/lib/graphql/directive/skip_directive.rb +1 -1
  18. data/lib/graphql/directive.rb +0 -4
  19. data/lib/graphql/enum_type.rb +5 -1
  20. data/lib/graphql/execution/errors.rb +1 -0
  21. data/lib/graphql/execution/interpreter/arguments.rb +1 -1
  22. data/lib/graphql/execution/interpreter/arguments_cache.rb +2 -2
  23. data/lib/graphql/execution/interpreter/runtime.rb +30 -18
  24. data/lib/graphql/execution/lookahead.rb +2 -2
  25. data/lib/graphql/execution/multiplex.rb +4 -1
  26. data/lib/graphql/float_type.rb +1 -1
  27. data/lib/graphql/id_type.rb +1 -1
  28. data/lib/graphql/int_type.rb +1 -1
  29. data/lib/graphql/introspection/directive_type.rb +1 -1
  30. data/lib/graphql/introspection/entry_points.rb +2 -2
  31. data/lib/graphql/introspection/enum_value_type.rb +2 -2
  32. data/lib/graphql/introspection/field_type.rb +2 -2
  33. data/lib/graphql/introspection/input_value_type.rb +4 -4
  34. data/lib/graphql/introspection/schema_type.rb +2 -2
  35. data/lib/graphql/introspection/type_type.rb +10 -10
  36. data/lib/graphql/language/block_string.rb +2 -6
  37. data/lib/graphql/language/document_from_schema_definition.rb +4 -2
  38. data/lib/graphql/language/lexer.rb +0 -3
  39. data/lib/graphql/language/lexer.rl +0 -4
  40. data/lib/graphql/language/nodes.rb +12 -2
  41. data/lib/graphql/language/parser.rb +442 -434
  42. data/lib/graphql/language/parser.y +5 -4
  43. data/lib/graphql/language/printer.rb +6 -1
  44. data/lib/graphql/language/sanitized_printer.rb +5 -5
  45. data/lib/graphql/language/token.rb +0 -4
  46. data/lib/graphql/name_validator.rb +0 -4
  47. data/lib/graphql/query/arguments.rb +1 -1
  48. data/lib/graphql/query/arguments_cache.rb +1 -1
  49. data/lib/graphql/query/context.rb +15 -2
  50. data/lib/graphql/query/literal_input.rb +1 -1
  51. data/lib/graphql/query/null_context.rb +12 -7
  52. data/lib/graphql/query/serial_execution/field_resolution.rb +1 -1
  53. data/lib/graphql/query/variables.rb +5 -1
  54. data/lib/graphql/relay/edges_instrumentation.rb +0 -1
  55. data/lib/graphql/relay/global_id_resolve.rb +1 -1
  56. data/lib/graphql/relay/page_info.rb +1 -1
  57. data/lib/graphql/rubocop/graphql/base_cop.rb +36 -0
  58. data/lib/graphql/rubocop/graphql/default_null_true.rb +43 -0
  59. data/lib/graphql/rubocop/graphql/default_required_true.rb +43 -0
  60. data/lib/graphql/rubocop.rb +4 -0
  61. data/lib/graphql/schema/addition.rb +37 -28
  62. data/lib/graphql/schema/argument.rb +8 -6
  63. data/lib/graphql/schema/build_from_definition.rb +5 -5
  64. data/lib/graphql/schema/directive/feature.rb +1 -1
  65. data/lib/graphql/schema/directive/flagged.rb +2 -2
  66. data/lib/graphql/schema/directive/include.rb +1 -1
  67. data/lib/graphql/schema/directive/skip.rb +1 -1
  68. data/lib/graphql/schema/directive/transform.rb +1 -1
  69. data/lib/graphql/schema/directive.rb +7 -3
  70. data/lib/graphql/schema/enum.rb +60 -10
  71. data/lib/graphql/schema/enum_value.rb +6 -0
  72. data/lib/graphql/schema/field/connection_extension.rb +1 -1
  73. data/lib/graphql/schema/field.rb +130 -39
  74. data/lib/graphql/schema/field_extension.rb +52 -2
  75. data/lib/graphql/schema/find_inherited_value.rb +1 -0
  76. data/lib/graphql/schema/finder.rb +5 -5
  77. data/lib/graphql/schema/input_object.rb +8 -5
  78. data/lib/graphql/schema/interface.rb +11 -20
  79. data/lib/graphql/schema/introspection_system.rb +1 -1
  80. data/lib/graphql/schema/list.rb +3 -1
  81. data/lib/graphql/schema/member/accepts_definition.rb +15 -3
  82. data/lib/graphql/schema/member/build_type.rb +0 -4
  83. data/lib/graphql/schema/member/cached_graphql_definition.rb +29 -2
  84. data/lib/graphql/schema/member/has_arguments.rb +55 -13
  85. data/lib/graphql/schema/member/has_deprecation_reason.rb +1 -1
  86. data/lib/graphql/schema/member/has_fields.rb +76 -18
  87. data/lib/graphql/schema/member/has_interfaces.rb +90 -0
  88. data/lib/graphql/schema/member.rb +1 -0
  89. data/lib/graphql/schema/non_null.rb +3 -1
  90. data/lib/graphql/schema/object.rb +10 -75
  91. data/lib/graphql/schema/printer.rb +1 -1
  92. data/lib/graphql/schema/relay_classic_mutation.rb +29 -3
  93. data/lib/graphql/schema/resolver/has_payload_type.rb +27 -2
  94. data/lib/graphql/schema/resolver.rb +19 -5
  95. data/lib/graphql/schema/scalar.rb +2 -0
  96. data/lib/graphql/schema/subscription.rb +11 -1
  97. data/lib/graphql/schema/traversal.rb +1 -1
  98. data/lib/graphql/schema/type_expression.rb +1 -1
  99. data/lib/graphql/schema/type_membership.rb +18 -4
  100. data/lib/graphql/schema/union.rb +8 -1
  101. data/lib/graphql/schema/validator/format_validator.rb +3 -5
  102. data/lib/graphql/schema/validator/numericality_validator.rb +1 -0
  103. data/lib/graphql/schema/validator.rb +4 -7
  104. data/lib/graphql/schema/warden.rb +116 -52
  105. data/lib/graphql/schema.rb +106 -22
  106. data/lib/graphql/static_validation/base_visitor.rb +5 -5
  107. data/lib/graphql/static_validation/definition_dependencies.rb +0 -1
  108. data/lib/graphql/static_validation/literal_validator.rb +1 -1
  109. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  110. data/lib/graphql/static_validation/rules/fields_will_merge.rb +15 -8
  111. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +3 -1
  112. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +4 -4
  113. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +7 -7
  114. data/lib/graphql/string_type.rb +1 -1
  115. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +8 -4
  116. data/lib/graphql/subscriptions/event.rb +20 -12
  117. data/lib/graphql/subscriptions.rb +17 -19
  118. data/lib/graphql/types/relay/connection_behaviors.rb +26 -9
  119. data/lib/graphql/types/relay/default_relay.rb +5 -1
  120. data/lib/graphql/types/relay/edge_behaviors.rb +13 -2
  121. data/lib/graphql/types/relay/has_node_field.rb +1 -1
  122. data/lib/graphql/types/relay/has_nodes_field.rb +1 -1
  123. data/lib/graphql/version.rb +1 -1
  124. data/lib/graphql.rb +10 -32
  125. metadata +10 -5
@@ -92,6 +92,8 @@ module GraphQL
92
92
  end
93
93
  end
94
94
 
95
+ class DuplicateNamesError < GraphQL::Error; end
96
+
95
97
  class UnresolvedLateBoundTypeError < GraphQL::Error
96
98
  attr_reader :type
97
99
  def initialize(type:)
@@ -843,7 +845,7 @@ module GraphQL
843
845
  # - Cause the Schema instance to be created, if it hasn't been created yet
844
846
  # - Delegate to that instance
845
847
  # Eventually, the methods will be moved into this class, removing the need for the singleton.
846
- def_delegators :graphql_definition,
848
+ def_delegators :deprecated_graphql_definition,
847
849
  # Execution
848
850
  :execution_strategy_for_operation,
849
851
  # Configuration
@@ -852,6 +854,10 @@ module GraphQL
852
854
  :id_from_object=, :object_from_id=,
853
855
  :remove_handler
854
856
 
857
+ def deprecated_graphql_definition
858
+ graphql_definition(silence_deprecation_warning: true)
859
+ end
860
+
855
861
  # @return [GraphQL::Subscriptions]
856
862
  attr_accessor :subscriptions
857
863
 
@@ -894,8 +900,15 @@ module GraphQL
894
900
  @find_cache[path] ||= @finder.find(path)
895
901
  end
896
902
 
897
- def graphql_definition
898
- @graphql_definition ||= to_graphql
903
+ def graphql_definition(silence_deprecation_warning: false)
904
+ @graphql_definition ||= begin
905
+ unless silence_deprecation_warning
906
+ message = "Legacy `.graphql_definition` objects are deprecated and will be removed in GraphQL-Ruby 2.0. Use a class-based definition instead."
907
+ caller_message = "\n\nCalled on #{self.inspect} from:\n #{caller(1, 25).map { |l| " #{l}" }.join("\n")}"
908
+ GraphQL::Deprecation.warn(message + caller_message)
909
+ end
910
+ to_graphql(silence_deprecation_warning: silence_deprecation_warning)
911
+ end
899
912
  end
900
913
 
901
914
  def default_filter
@@ -927,19 +940,20 @@ module GraphQL
927
940
  find_inherited_value(:plugins, EMPTY_ARRAY) + own_plugins
928
941
  end
929
942
 
943
+ prepend Schema::Member::CachedGraphQLDefinition::DeprecatedToGraphQL
930
944
  def to_graphql
931
945
  schema_defn = self.new
932
946
  schema_defn.raise_definition_error = true
933
- schema_defn.query = query && query.graphql_definition
934
- schema_defn.mutation = mutation && mutation.graphql_definition
935
- schema_defn.subscription = subscription && subscription.graphql_definition
947
+ schema_defn.query = query && query.graphql_definition(silence_deprecation_warning: true)
948
+ schema_defn.mutation = mutation && mutation.graphql_definition(silence_deprecation_warning: true)
949
+ schema_defn.subscription = subscription && subscription.graphql_definition(silence_deprecation_warning: true)
936
950
  schema_defn.validate_timeout = validate_timeout
937
951
  schema_defn.validate_max_errors = validate_max_errors
938
952
  schema_defn.max_complexity = max_complexity
939
953
  schema_defn.error_bubbling = error_bubbling
940
954
  schema_defn.max_depth = max_depth
941
955
  schema_defn.default_max_page_size = default_max_page_size
942
- schema_defn.orphan_types = orphan_types.map(&:graphql_definition)
956
+ schema_defn.orphan_types = orphan_types.map { |t| t.graphql_definition(silence_deprecation_warning: true) }
943
957
  schema_defn.disable_introspection_entry_points = disable_introspection_entry_points?
944
958
  schema_defn.disable_schema_introspection_entry_point = disable_schema_introspection_entry_point?
945
959
  schema_defn.disable_type_introspection_entry_point = disable_type_introspection_entry_point?
@@ -996,16 +1010,58 @@ module GraphQL
996
1010
  # Build a map of `{ name => type }` and return it
997
1011
  # @return [Hash<String => Class>] A dictionary of type classes by their GraphQL name
998
1012
  # @see get_type Which is more efficient for finding _one type_ by name, because it doesn't merge hashes.
999
- def types
1000
- non_introspection_types.merge(introspection_system.types)
1013
+ def types(context = GraphQL::Query::NullContext)
1014
+ all_types = non_introspection_types.merge(introspection_system.types)
1015
+ visible_types = {}
1016
+ all_types.each do |k, v|
1017
+ visible_types[k] =if v.is_a?(Array)
1018
+ visible_t = nil
1019
+ v.each do |t|
1020
+ if t.visible?(context)
1021
+ if visible_t.nil?
1022
+ visible_t = t
1023
+ else
1024
+ raise DuplicateNamesError, "Found two visible type definitions for `#{k}`: #{visible_t.inspect}, #{t.inspect}"
1025
+ end
1026
+ end
1027
+ end
1028
+ visible_t
1029
+ else
1030
+ v
1031
+ end
1032
+ end
1033
+ visible_types
1001
1034
  end
1002
1035
 
1003
1036
  # @param type_name [String]
1004
1037
  # @return [Module, nil] A type, or nil if there's no type called `type_name`
1005
- def get_type(type_name)
1006
- own_types[type_name] ||
1007
- introspection_system.types[type_name] ||
1008
- find_inherited_value(:types, EMPTY_HASH)[type_name]
1038
+ def get_type(type_name, context = GraphQL::Query::NullContext)
1039
+ local_entry = own_types[type_name]
1040
+ type_defn = case local_entry
1041
+ when nil
1042
+ nil
1043
+ when Array
1044
+ visible_t = nil
1045
+ warden = Warden.from_context(context)
1046
+ local_entry.each do |t|
1047
+ if warden.visible_type?(t, context)
1048
+ if visible_t.nil?
1049
+ visible_t = t
1050
+ else
1051
+ raise DuplicateNamesError, "Found two visible type definitions for `#{type_name}`: #{visible_t.inspect}, #{t.inspect}"
1052
+ end
1053
+ end
1054
+ end
1055
+ visible_t
1056
+ when Module
1057
+ local_entry
1058
+ else
1059
+ raise "Invariant: unexpected own_types[#{type_name.inspect}]: #{local_entry.inspect}"
1060
+ end
1061
+
1062
+ type_defn ||
1063
+ introspection_system.types[type_name] || # todo context-specific introspection?
1064
+ (superclass.respond_to?(:get_type) ? superclass.get_type(type_name, context) : nil)
1009
1065
  end
1010
1066
 
1011
1067
  # @api private
@@ -1182,19 +1238,19 @@ module GraphQL
1182
1238
  GraphQL::Schema::TypeExpression.build_type(type_owner, ast_node)
1183
1239
  end
1184
1240
 
1185
- def get_field(type_or_name, field_name)
1241
+ def get_field(type_or_name, field_name, context = GraphQL::Query::NullContext)
1186
1242
  parent_type = case type_or_name
1187
1243
  when LateBoundType
1188
- get_type(type_or_name.name)
1244
+ get_type(type_or_name.name, context)
1189
1245
  when String
1190
- get_type(type_or_name)
1246
+ get_type(type_or_name, context)
1191
1247
  when Module
1192
1248
  type_or_name
1193
1249
  else
1194
1250
  raise ArgumentError, "unexpected field owner for #{field_name.inspect}: #{type_or_name.inspect} (#{type_or_name.class})"
1195
1251
  end
1196
1252
 
1197
- if parent_type.kind.fields? && (field = parent_type.get_field(field_name))
1253
+ if parent_type.kind.fields? && (field = parent_type.get_field(field_name, context))
1198
1254
  field
1199
1255
  elsif parent_type == query && (entry_point_field = introspection_system.entry_point(name: field_name))
1200
1256
  entry_point_field
@@ -1205,8 +1261,8 @@ module GraphQL
1205
1261
  end
1206
1262
  end
1207
1263
 
1208
- def get_fields(type)
1209
- type.fields
1264
+ def get_fields(type, context = GraphQL::Query::NullContext)
1265
+ type.fields(context)
1210
1266
  end
1211
1267
 
1212
1268
  def introspection(new_introspection_namespace = nil)
@@ -1405,7 +1461,6 @@ module GraphQL
1405
1461
  if new_orphan_types.any?
1406
1462
  new_orphan_types = new_orphan_types.flatten
1407
1463
  add_type_and_traverse(new_orphan_types, root: false)
1408
- @orphan_types = new_orphan_types
1409
1464
  own_orphan_types.concat(new_orphan_types.flatten)
1410
1465
  end
1411
1466
 
@@ -1715,7 +1770,7 @@ module GraphQL
1715
1770
  if subscription.singleton_class.ancestors.include?(Subscriptions::SubscriptionRoot)
1716
1771
  GraphQL::Deprecation.warn("`extend Subscriptions::SubscriptionRoot` is no longer required; you may remove it from #{self}'s `subscription` root type (#{subscription}).")
1717
1772
  else
1718
- subscription.fields.each do |name, field|
1773
+ subscription.all_field_definitions.each do |field|
1719
1774
  field.extension(Subscriptions::DefaultSubscriptionResolveExtension)
1720
1775
  end
1721
1776
  end
@@ -1737,7 +1792,36 @@ module GraphQL
1737
1792
  end
1738
1793
  new_types = Array(t)
1739
1794
  addition = Schema::Addition.new(schema: self, own_types: own_types, new_types: new_types)
1740
- own_types.merge!(addition.types)
1795
+ addition.types.each do |name, types_entry| # rubocop:disable Development/ContextIsPassedCop -- build-time, not query-time
1796
+ if (prev_entry = own_types[name])
1797
+ prev_entries = case prev_entry
1798
+ when Array
1799
+ prev_entry
1800
+ when Module
1801
+ own_types[name] = [prev_entry]
1802
+ else
1803
+ raise "Invariant: unexpected prev_entry at #{name.inspect} when adding #{t.inspect}"
1804
+ end
1805
+
1806
+ case types_entry
1807
+ when Array
1808
+ prev_entries.concat(types_entry)
1809
+ prev_entries.uniq! # in case any are being re-visited
1810
+ when Module
1811
+ if !prev_entries.include?(types_entry)
1812
+ prev_entries << types_entry
1813
+ end
1814
+ else
1815
+ raise "Invariant: unexpected types_entry at #{name} when adding #{t.inspect}"
1816
+ end
1817
+ else
1818
+ if types_entry.is_a?(Array)
1819
+ types_entry.uniq!
1820
+ end
1821
+ own_types[name] = types_entry
1822
+ end
1823
+ end
1824
+
1741
1825
  own_possible_types.merge!(addition.possible_types) { |key, old_val, new_val| old_val + new_val }
1742
1826
  own_union_memberships.merge!(addition.union_memberships)
1743
1827
 
@@ -94,7 +94,7 @@ module GraphQL
94
94
 
95
95
  def on_field(node, parent)
96
96
  parent_type = @object_types.last
97
- field_definition = @schema.get_field(parent_type, node.name)
97
+ field_definition = @schema.get_field(parent_type, node.name, @context.query.context)
98
98
  @field_definitions.push(field_definition)
99
99
  if !field_definition.nil?
100
100
  next_object_type = field_definition.type.unwrap
@@ -120,14 +120,14 @@ module GraphQL
120
120
  argument_defn = if (arg = @argument_definitions.last)
121
121
  arg_type = arg.type.unwrap
122
122
  if arg_type.kind.input_object?
123
- arg_type.arguments[node.name]
123
+ @context.warden.get_argument(arg_type, node.name)
124
124
  else
125
125
  nil
126
126
  end
127
127
  elsif (directive_defn = @directive_definitions.last)
128
- directive_defn.arguments[node.name]
128
+ @context.warden.get_argument(directive_defn, node.name)
129
129
  elsif (field_defn = @field_definitions.last)
130
- field_defn.arguments[node.name]
130
+ @context.warden.get_argument(field_defn, node.name)
131
131
  else
132
132
  nil
133
133
  end
@@ -187,7 +187,7 @@ module GraphQL
187
187
 
188
188
  def on_fragment_with_type(node)
189
189
  object_type = if node.type
190
- @schema.get_type(node.type.name)
190
+ @context.warden.get_type(node.type.name)
191
191
  else
192
192
  @object_types.last
193
193
  end
@@ -70,7 +70,6 @@ module GraphQL
70
70
  @dependency_map ||= resolve_dependencies(&block)
71
71
  end
72
72
 
73
-
74
73
  # Map definition AST nodes to the definition AST nodes they depend on.
75
74
  # Expose circular dependencies.
76
75
  class DependencyMap
@@ -95,7 +95,7 @@ module GraphQL
95
95
  def required_input_fields_are_present(type, ast_node)
96
96
  # TODO - would be nice to use these to create an error message so the caller knows
97
97
  # that required fields are missing
98
- required_field_names = type.arguments.each_value
98
+ required_field_names = @warden.arguments(type)
99
99
  .select { |argument| argument.type.kind.non_null? && @warden.get_argument(type, argument.name) }
100
100
  .map(&:name)
101
101
 
@@ -15,7 +15,7 @@ module GraphQL
15
15
  if @context.schema.error_bubbling || context.errors.none? { |err| err.path.take(@path.size) == @path }
16
16
  parent_defn = parent_definition(parent)
17
17
 
18
- if parent_defn && (arg_defn = parent_defn.arguments[node.name])
18
+ if parent_defn && (arg_defn = context.warden.get_argument(parent_defn, node.name))
19
19
  validation_result = context.validate_literal(node.value, arg_defn.type)
20
20
  if !validation_result.valid?
21
21
  kind_of_node = node_type(parent)
@@ -50,15 +50,15 @@ module GraphQL
50
50
  @arg_conflicts = nil
51
51
 
52
52
  yield
53
-
54
- field_conflicts.each_value { |error| add_error(error) }
55
- arg_conflicts.each_value { |error| add_error(error) }
53
+ # don't initialize these if they weren't initialized in the block:
54
+ @field_conflicts && @field_conflicts.each_value { |error| add_error(error) }
55
+ @arg_conflicts && @arg_conflicts.each_value { |error| add_error(error) }
56
56
  end
57
57
 
58
58
  def conflicts_within_selection_set(node, parent_type)
59
59
  return if parent_type.nil?
60
60
 
61
- fields, fragment_spreads = fields_and_fragments_from_selection(node, owner_type: parent_type, parents: [])
61
+ fields, fragment_spreads = fields_and_fragments_from_selection(node, owner_type: parent_type, parents: nil)
62
62
 
63
63
  # (A) Find find all conflicts "within" the fields of this selection set.
64
64
  find_conflicts_within(fields)
@@ -198,10 +198,14 @@ module GraphQL
198
198
  response_keys.each do |key, fields|
199
199
  next if fields.size < 2
200
200
  # find conflicts within nodes
201
- for i in 0..fields.size - 1
202
- for j in i + 1..fields.size - 1
201
+ i = 0
202
+ while i < fields.size
203
+ j = i + 1
204
+ while j < fields.size
203
205
  find_conflict(key, fields[i], fields[j])
206
+ j += 1
204
207
  end
208
+ i += 1
205
209
  end
206
210
  end
207
211
  end
@@ -243,7 +247,9 @@ module GraphQL
243
247
  end
244
248
 
245
249
  def find_conflicts_between_sub_selection_sets(field1, field2, mutually_exclusive:)
246
- return if field1.definition.nil? || field2.definition.nil?
250
+ return if field1.definition.nil? ||
251
+ field2.definition.nil? ||
252
+ (field1.node.selections.empty? && field2.node.selections.empty?)
247
253
 
248
254
  return_type1 = field1.definition.type.unwrap
249
255
  return_type2 = field2.definition.type.unwrap
@@ -323,6 +329,7 @@ module GraphQL
323
329
  if node.selections.empty?
324
330
  NO_SELECTIONS
325
331
  else
332
+ parents ||= []
326
333
  fields, fragment_spreads = find_fields_and_fragments(node.selections, owner_type: owner_type, parents: parents, fields: [], fragment_spreads: [])
327
334
  response_keys = fields.group_by { |f| f.node.alias || f.node.name }
328
335
  [response_keys, fragment_spreads]
@@ -333,7 +340,7 @@ module GraphQL
333
340
  selections.each do |node|
334
341
  case node
335
342
  when GraphQL::Language::Nodes::Field
336
- definition = context.schema.get_field(owner_type, node.name)
343
+ definition = context.query.get_field(owner_type, node.name)
337
344
  fields << Field.new(node, definition, owner_type, parents)
338
345
  when GraphQL::Language::Nodes::InlineFragment
339
346
  fragment_type = node.type ? context.warden.get_type(node.type.name) : owner_type
@@ -16,8 +16,10 @@ module GraphQL
16
16
  private
17
17
 
18
18
  def assert_required_args(ast_node, defn)
19
+ args = defn.arguments(context.query.context)
20
+ return if args.empty?
19
21
  present_argument_names = ast_node.arguments.map(&:name)
20
- required_argument_names = defn.arguments.each_value
22
+ required_argument_names = context.warden.arguments(defn)
21
23
  .select { |a| a.type.kind.non_null? && !a.default_value? && context.warden.get_argument(defn, a.name) }
22
24
  .map(&:name)
23
25
 
@@ -34,16 +34,16 @@ module GraphQL
34
34
  parent_type = get_parent_type(context, parent)
35
35
  return unless parent_type && parent_type.kind.input_object?
36
36
 
37
- required_fields = parent_type.arguments
38
- .select{|k,v| v.type.kind.non_null?}
39
- .keys
37
+ required_fields = context.warden.arguments(parent_type)
38
+ .select{|arg| arg.type.kind.non_null?}
39
+ .map(&:graphql_name)
40
40
 
41
41
  present_fields = ast_node.arguments.map(&:name)
42
42
  missing_fields = required_fields - present_fields
43
43
 
44
44
  missing_fields.each do |missing_field|
45
45
  path = [*context.path, missing_field]
46
- missing_field_type = parent_type.arguments[missing_field].type
46
+ missing_field_type = context.warden.get_argument(parent_type, missing_field).type
47
47
  add_error(RequiredInputObjectAttributesArePresentError.new(
48
48
  "Argument '#{missing_field}' on InputObject '#{parent_type.to_type_signature}' is required. Expected type #{missing_field_type.to_type_signature}",
49
49
  argument_name: missing_field,
@@ -22,15 +22,15 @@ module GraphQL
22
22
  node_values = node_values.select { |value| value.is_a? GraphQL::Language::Nodes::VariableIdentifier }
23
23
 
24
24
  if node_values.any?
25
- arguments = case parent
25
+ argument_owner = case parent
26
26
  when GraphQL::Language::Nodes::Field
27
- context.field_definition.arguments
27
+ context.field_definition
28
28
  when GraphQL::Language::Nodes::Directive
29
- context.directive_definition.arguments
29
+ context.directive_definition
30
30
  when GraphQL::Language::Nodes::InputObject
31
31
  arg_type = context.argument_definition.type.unwrap
32
32
  if arg_type.kind.input_object?
33
- arguments = arg_type.arguments
33
+ arg_type
34
34
  else
35
35
  # This is some kind of error
36
36
  nil
@@ -43,7 +43,7 @@ module GraphQL
43
43
  var_defn_ast = @declared_variables[node_value.name]
44
44
  # Might be undefined :(
45
45
  # VariablesAreUsedAndDefined can't finalize its search until the end of the document.
46
- var_defn_ast && arguments && validate_usage(arguments, node, var_defn_ast)
46
+ var_defn_ast && argument_owner && validate_usage(argument_owner, node, var_defn_ast)
47
47
  end
48
48
  end
49
49
  super
@@ -51,7 +51,7 @@ module GraphQL
51
51
 
52
52
  private
53
53
 
54
- def validate_usage(arguments, arg_node, ast_var)
54
+ def validate_usage(argument_owner, arg_node, ast_var)
55
55
  var_type = context.schema.type_from_ast(ast_var.type, context: context)
56
56
  if var_type.nil?
57
57
  return
@@ -65,7 +65,7 @@ module GraphQL
65
65
  end
66
66
  end
67
67
 
68
- arg_defn = arguments[arg_node.name]
68
+ arg_defn = context.warden.get_argument(argument_owner, arg_node.name)
69
69
  arg_defn_type = arg_defn.type
70
70
 
71
71
  var_inner_type = var_type.unwrap
@@ -1,2 +1,2 @@
1
1
  # frozen_string_literal: true
2
- GraphQL::STRING_TYPE = GraphQL::Types::String.graphql_definition
2
+ GraphQL::STRING_TYPE = GraphQL::Types::String.graphql_definition(silence_deprecation_warning: true)
@@ -170,10 +170,12 @@ module GraphQL
170
170
  first_subscription_id = first_event.context.fetch(:subscription_id)
171
171
  object ||= load_action_cable_message(message, first_event.context)
172
172
  result = execute_update(first_subscription_id, first_event, object)
173
- # Having calculated the result _once_, send the same payload to all subscribers
174
- events.each do |event|
175
- subscription_id = event.context.fetch(:subscription_id)
176
- deliver(subscription_id, result)
173
+ if !result.nil?
174
+ # Having calculated the result _once_, send the same payload to all subscribers
175
+ events.each do |event|
176
+ subscription_id = event.context.fetch(:subscription_id)
177
+ deliver(subscription_id, result)
178
+ end
177
179
  end
178
180
  end
179
181
  end
@@ -214,6 +216,8 @@ module GraphQL
214
216
  # The channel was closed, forget about it.
215
217
  def delete_subscription(subscription_id)
216
218
  query = @subscriptions.delete(subscription_id)
219
+ # In case this came from the server, tell the client to unsubscribe:
220
+ @action_cable.server.broadcast(stream_subscription_name(subscription_id), { more: false })
217
221
  # This can be `nil` when `.trigger` happens inside an unsubscribed ActionCable channel,
218
222
  # see https://github.com/rmosolgo/graphql-ruby/issues/2478
219
223
  if query
@@ -23,15 +23,23 @@ module GraphQL
23
23
  @arguments = arguments
24
24
  @context = context
25
25
  field ||= context.field
26
- scope_val = scope || (context && field.subscription_scope && context[field.subscription_scope])
26
+ scope_key = field.subscription_scope
27
+ scope_val = scope || (context && scope_key && context[scope_key])
28
+ if scope_key &&
29
+ (subscription = field.resolver) &&
30
+ (subscription.respond_to?(:subscription_scope_optional?)) &&
31
+ !subscription.subscription_scope_optional? &&
32
+ scope_val.nil?
33
+ raise Subscriptions::SubscriptionScopeMissingError, "#{field.path} (#{subscription}) requires a `scope:` value to trigger updates (Set `subscription_scope ..., optional: true` to disable this requirement)"
34
+ end
27
35
 
28
- @topic = self.class.serialize(name, arguments, field, scope: scope_val)
36
+ @topic = self.class.serialize(name, arguments, field, scope: scope_val, context: context)
29
37
  end
30
38
 
31
39
  # @return [String] an identifier for this unit of subscription
32
- def self.serialize(_name, arguments, field, scope:)
40
+ def self.serialize(_name, arguments, field, scope:, context: GraphQL::Query::NullContext)
33
41
  subscription = field.resolver || GraphQL::Schema::Subscription
34
- normalized_args = stringify_args(field, arguments.to_h)
42
+ normalized_args = stringify_args(field, arguments.to_h, context)
35
43
  subscription.topic_for(arguments: normalized_args, field: field, scope: scope)
36
44
  end
37
45
 
@@ -83,7 +91,7 @@ module GraphQL
83
91
  end
84
92
  end
85
93
 
86
- def stringify_args(arg_owner, args)
94
+ def stringify_args(arg_owner, args, context)
87
95
  arg_owner = arg_owner.respond_to?(:unwrap) ? arg_owner.unwrap : arg_owner # remove list and non-null wrappers
88
96
  case args
89
97
  when Hash
@@ -91,13 +99,13 @@ module GraphQL
91
99
  args.each do |k, v|
92
100
  arg_name = k.to_s
93
101
  camelized_arg_name = GraphQL::Schema::Member::BuildType.camelize(arg_name)
94
- arg_defn = get_arg_definition(arg_owner, camelized_arg_name)
102
+ arg_defn = get_arg_definition(arg_owner, camelized_arg_name, context)
95
103
 
96
104
  if arg_defn
97
105
  normalized_arg_name = camelized_arg_name
98
106
  else
99
107
  normalized_arg_name = arg_name
100
- arg_defn = get_arg_definition(arg_owner, normalized_arg_name)
108
+ arg_defn = get_arg_definition(arg_owner, normalized_arg_name, context)
101
109
  end
102
110
  arg_base_type = arg_defn.type.unwrap
103
111
  # In the case where the value being emitted is seen as a "JSON"
@@ -113,22 +121,22 @@ module GraphQL
113
121
  end
114
122
  next_args[normalized_arg_name] = sorted_value.respond_to?(:to_json) ? sorted_value.to_json : sorted_value
115
123
  else
116
- next_args[normalized_arg_name] = stringify_args(arg_base_type, v)
124
+ next_args[normalized_arg_name] = stringify_args(arg_base_type, v, context)
117
125
  end
118
126
  end
119
127
  # Make sure they're deeply sorted
120
128
  next_args.sort.to_h
121
129
  when Array
122
- args.map { |a| stringify_args(arg_owner, a) }
130
+ args.map { |a| stringify_args(arg_owner, a, context) }
123
131
  when GraphQL::Schema::InputObject
124
- stringify_args(arg_owner, args.to_h)
132
+ stringify_args(arg_owner, args.to_h, context)
125
133
  else
126
134
  args
127
135
  end
128
136
  end
129
137
 
130
- def get_arg_definition(arg_owner, arg_name)
131
- arg_owner.arguments[arg_name] || arg_owner.arguments.each_value.find { |v| v.keyword.to_s == arg_name }
138
+ def get_arg_definition(arg_owner, arg_name, context)
139
+ arg_owner.get_argument(arg_name, context) || arg_owner.arguments(context).each_value.find { |v| v.keyword.to_s == arg_name }
132
140
  end
133
141
  end
134
142
  end
@@ -16,6 +16,13 @@ module GraphQL
16
16
  class InvalidTriggerError < GraphQL::Error
17
17
  end
18
18
 
19
+ # Raised when either:
20
+ # - An initial subscription didn't have a value for `context[subscription_scope]`
21
+ # - Or, an update didn't pass `.trigger(..., scope:)`
22
+ # When raised, the initial subscription or update fails completely.
23
+ class SubscriptionScopeMissingError < GraphQL::Error
24
+ end
25
+
19
26
  # @see {Subscriptions#initialize} for options, concrete implementations may add options.
20
27
  def self.use(defn, options = {})
21
28
  schema = defn.is_a?(Class) ? defn : defn.target
@@ -76,7 +83,8 @@ module GraphQL
76
83
  end
77
84
 
78
85
  # Normalize symbol-keyed args to strings, try camelizing them
79
- normalized_args = normalize_arguments(normalized_event_name, field, args)
86
+ # Should this accept a real context somehow?
87
+ normalized_args = normalize_arguments(normalized_event_name, field, args, GraphQL::Query::NullContext)
80
88
 
81
89
  event = Subscriptions::Event.new(
82
90
  name: normalized_event_name,
@@ -151,16 +159,6 @@ module GraphQL
151
159
  # @param object [Object]
152
160
  # @return [void]
153
161
  def execute_all(event, object)
154
- each_subscription_id(event) do |subscription_id|
155
- execute(subscription_id, event, object)
156
- end
157
- end
158
-
159
- # Get each `subscription_id` subscribed to `event.topic` and yield them
160
- # @param event [GraphQL::Subscriptions::Event]
161
- # @yieldparam subscription_id [String]
162
- # @return [void]
163
- def each_subscription_id(event)
164
162
  raise GraphQL::RequiredImplementationMissingError
165
163
  end
166
164
 
@@ -233,7 +231,7 @@ module GraphQL
233
231
  # @param arg_owner [GraphQL::Field, GraphQL::BaseType]
234
232
  # @param args [Hash, Array, Any] some GraphQL input value to coerce as `arg_owner`
235
233
  # @return [Any] normalized arguments value
236
- def normalize_arguments(event_name, arg_owner, args)
234
+ def normalize_arguments(event_name, arg_owner, args, context)
237
235
  case arg_owner
238
236
  when GraphQL::Field, GraphQL::InputObjectType, GraphQL::Schema::Field, Class
239
237
  if arg_owner.is_a?(Class) && !arg_owner.kind.input_object?
@@ -244,19 +242,19 @@ module GraphQL
244
242
  missing_arg_names = []
245
243
  args.each do |k, v|
246
244
  arg_name = k.to_s
247
- arg_defn = arg_owner.arguments[arg_name]
245
+ arg_defn = arg_owner.get_argument(arg_name, context)
248
246
  if arg_defn
249
247
  normalized_arg_name = arg_name
250
248
  else
251
249
  normalized_arg_name = normalize_name(arg_name)
252
- arg_defn = arg_owner.arguments[normalized_arg_name]
250
+ arg_defn = arg_owner.get_argument(normalized_arg_name, context)
253
251
  end
254
252
 
255
253
  if arg_defn
256
254
  if arg_defn.loads
257
255
  normalized_arg_name = arg_defn.keyword.to_s
258
256
  end
259
- normalized = normalize_arguments(event_name, arg_defn.type, v)
257
+ normalized = normalize_arguments(event_name, arg_defn.type, v, context)
260
258
  normalized_args[normalized_arg_name] = normalized
261
259
  else
262
260
  # Couldn't find a matching argument definition
@@ -266,12 +264,12 @@ module GraphQL
266
264
 
267
265
  # Backfill default values so that trigger arguments
268
266
  # match query arguments.
269
- arg_owner.arguments.each do |name, arg_defn|
267
+ arg_owner.arguments(context).each do |_name, arg_defn|
270
268
  if arg_defn.default_value? && !normalized_args.key?(arg_defn.name)
271
269
  default_value = arg_defn.default_value
272
270
  # We don't have an underlying "object" here, so it can't call methods.
273
271
  # This is broken.
274
- normalized_args[arg_defn.name] = arg_defn.prepare_value(nil, default_value, context: GraphQL::Query::NullContext)
272
+ normalized_args[arg_defn.name] = arg_defn.prepare_value(nil, default_value, context: context)
275
273
  end
276
274
  end
277
275
 
@@ -290,9 +288,9 @@ module GraphQL
290
288
 
291
289
  normalized_args
292
290
  when GraphQL::ListType, GraphQL::Schema::List
293
- args.map { |a| normalize_arguments(event_name, arg_owner.of_type, a) }
291
+ args.map { |a| normalize_arguments(event_name, arg_owner.of_type, a, context) }
294
292
  when GraphQL::NonNullType, GraphQL::Schema::NonNull
295
- normalize_arguments(event_name, arg_owner.of_type, args)
293
+ normalize_arguments(event_name, arg_owner.of_type, args, context)
296
294
  else
297
295
  args
298
296
  end