graphql 1.12.18 → 1.13.0

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.

Potentially problematic release.


This version of graphql might be problematic. Click here for more details.

Files changed (120) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/mutation_generator.rb +1 -1
  3. data/lib/generators/graphql/object_generator.rb +2 -1
  4. data/lib/generators/graphql/relay.rb +19 -11
  5. data/lib/generators/graphql/templates/schema.erb +14 -2
  6. data/lib/generators/graphql/type_generator.rb +0 -1
  7. data/lib/graphql/analysis/ast/field_usage.rb +2 -2
  8. data/lib/graphql/analysis/ast/query_complexity.rb +10 -14
  9. data/lib/graphql/analysis/ast/visitor.rb +4 -4
  10. data/lib/graphql/backtrace/table.rb +1 -1
  11. data/lib/graphql/dataloader/source.rb +30 -2
  12. data/lib/graphql/dataloader.rb +55 -22
  13. data/lib/graphql/deprecation.rb +1 -5
  14. data/lib/graphql/directive.rb +0 -4
  15. data/lib/graphql/enum_type.rb +5 -1
  16. data/lib/graphql/execution/errors.rb +1 -0
  17. data/lib/graphql/execution/interpreter/arguments.rb +1 -1
  18. data/lib/graphql/execution/interpreter/arguments_cache.rb +2 -2
  19. data/lib/graphql/execution/interpreter/runtime.rb +20 -12
  20. data/lib/graphql/execution/lookahead.rb +2 -2
  21. data/lib/graphql/execution/multiplex.rb +1 -1
  22. data/lib/graphql/integer_encoding_error.rb +18 -2
  23. data/lib/graphql/introspection/directive_type.rb +1 -1
  24. data/lib/graphql/introspection/entry_points.rb +2 -2
  25. data/lib/graphql/introspection/enum_value_type.rb +2 -2
  26. data/lib/graphql/introspection/field_type.rb +2 -2
  27. data/lib/graphql/introspection/input_value_type.rb +4 -4
  28. data/lib/graphql/introspection/schema_type.rb +2 -2
  29. data/lib/graphql/introspection/type_type.rb +10 -10
  30. data/lib/graphql/language/block_string.rb +0 -4
  31. data/lib/graphql/language/document_from_schema_definition.rb +4 -2
  32. data/lib/graphql/language/lexer.rb +0 -3
  33. data/lib/graphql/language/lexer.rl +0 -4
  34. data/lib/graphql/language/nodes.rb +2 -1
  35. data/lib/graphql/language/parser.rb +442 -434
  36. data/lib/graphql/language/parser.y +5 -4
  37. data/lib/graphql/language/printer.rb +6 -1
  38. data/lib/graphql/language/sanitized_printer.rb +5 -5
  39. data/lib/graphql/language/token.rb +0 -4
  40. data/lib/graphql/name_validator.rb +0 -4
  41. data/lib/graphql/pagination/connections.rb +35 -16
  42. data/lib/graphql/query/arguments.rb +1 -1
  43. data/lib/graphql/query/arguments_cache.rb +1 -1
  44. data/lib/graphql/query/context.rb +5 -2
  45. data/lib/graphql/query/literal_input.rb +1 -1
  46. data/lib/graphql/query/null_context.rb +12 -7
  47. data/lib/graphql/query/serial_execution/field_resolution.rb +1 -1
  48. data/lib/graphql/query/validation_pipeline.rb +1 -1
  49. data/lib/graphql/query/variables.rb +5 -1
  50. data/lib/graphql/relay/edges_instrumentation.rb +0 -1
  51. data/lib/graphql/rubocop/graphql/base_cop.rb +36 -0
  52. data/lib/graphql/rubocop/graphql/default_null_true.rb +43 -0
  53. data/lib/graphql/rubocop/graphql/default_required_true.rb +43 -0
  54. data/lib/graphql/rubocop.rb +4 -0
  55. data/lib/graphql/schema/addition.rb +37 -28
  56. data/lib/graphql/schema/argument.rb +6 -6
  57. data/lib/graphql/schema/build_from_definition.rb +5 -5
  58. data/lib/graphql/schema/directive/feature.rb +1 -1
  59. data/lib/graphql/schema/directive/flagged.rb +2 -2
  60. data/lib/graphql/schema/directive/include.rb +1 -1
  61. data/lib/graphql/schema/directive/skip.rb +1 -1
  62. data/lib/graphql/schema/directive/transform.rb +1 -1
  63. data/lib/graphql/schema/directive.rb +2 -2
  64. data/lib/graphql/schema/enum.rb +57 -9
  65. data/lib/graphql/schema/enum_value.rb +4 -0
  66. data/lib/graphql/schema/field/connection_extension.rb +1 -1
  67. data/lib/graphql/schema/field.rb +92 -17
  68. data/lib/graphql/schema/find_inherited_value.rb +1 -0
  69. data/lib/graphql/schema/finder.rb +5 -5
  70. data/lib/graphql/schema/input_object.rb +6 -5
  71. data/lib/graphql/schema/interface.rb +8 -19
  72. data/lib/graphql/schema/member/accepts_definition.rb +8 -1
  73. data/lib/graphql/schema/member/build_type.rb +0 -4
  74. data/lib/graphql/schema/member/has_arguments.rb +62 -14
  75. data/lib/graphql/schema/member/has_deprecation_reason.rb +1 -1
  76. data/lib/graphql/schema/member/has_fields.rb +76 -18
  77. data/lib/graphql/schema/member/has_interfaces.rb +90 -0
  78. data/lib/graphql/schema/member.rb +1 -0
  79. data/lib/graphql/schema/object.rb +7 -74
  80. data/lib/graphql/schema/printer.rb +1 -1
  81. data/lib/graphql/schema/relay_classic_mutation.rb +29 -3
  82. data/lib/graphql/schema/resolver/has_payload_type.rb +27 -2
  83. data/lib/graphql/schema/resolver.rb +29 -5
  84. data/lib/graphql/schema/subscription.rb +11 -1
  85. data/lib/graphql/schema/type_expression.rb +1 -1
  86. data/lib/graphql/schema/type_membership.rb +18 -4
  87. data/lib/graphql/schema/union.rb +6 -1
  88. data/lib/graphql/schema/validator/allow_blank_validator.rb +29 -0
  89. data/lib/graphql/schema/validator/allow_null_validator.rb +26 -0
  90. data/lib/graphql/schema/validator/exclusion_validator.rb +3 -1
  91. data/lib/graphql/schema/validator/format_validator.rb +4 -5
  92. data/lib/graphql/schema/validator/inclusion_validator.rb +3 -1
  93. data/lib/graphql/schema/validator/length_validator.rb +5 -3
  94. data/lib/graphql/schema/validator/numericality_validator.rb +8 -1
  95. data/lib/graphql/schema/validator.rb +36 -25
  96. data/lib/graphql/schema/warden.rb +116 -52
  97. data/lib/graphql/schema.rb +87 -15
  98. data/lib/graphql/static_validation/base_visitor.rb +5 -5
  99. data/lib/graphql/static_validation/definition_dependencies.rb +0 -1
  100. data/lib/graphql/static_validation/error.rb +3 -1
  101. data/lib/graphql/static_validation/literal_validator.rb +1 -1
  102. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
  103. data/lib/graphql/static_validation/rules/fields_will_merge.rb +41 -22
  104. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +25 -4
  105. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +1 -1
  106. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +4 -4
  107. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +7 -7
  108. data/lib/graphql/static_validation/validation_context.rb +2 -1
  109. data/lib/graphql/string_encoding_error.rb +13 -3
  110. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +6 -4
  111. data/lib/graphql/subscriptions/event.rb +65 -13
  112. data/lib/graphql/subscriptions.rb +17 -19
  113. data/lib/graphql/types/int.rb +1 -1
  114. data/lib/graphql/types/relay/has_node_field.rb +1 -1
  115. data/lib/graphql/types/relay/has_nodes_field.rb +1 -1
  116. data/lib/graphql/types/string.rb +1 -1
  117. data/lib/graphql/unauthorized_error.rb +1 -1
  118. data/lib/graphql/version.rb +1 -1
  119. data/lib/graphql.rb +9 -31
  120. metadata +12 -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:)
@@ -996,16 +998,58 @@ module GraphQL
996
998
  # Build a map of `{ name => type }` and return it
997
999
  # @return [Hash<String => Class>] A dictionary of type classes by their GraphQL name
998
1000
  # @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)
1001
+ def types(context = GraphQL::Query::NullContext)
1002
+ all_types = non_introspection_types.merge(introspection_system.types)
1003
+ visible_types = {}
1004
+ all_types.each do |k, v|
1005
+ visible_types[k] =if v.is_a?(Array)
1006
+ visible_t = nil
1007
+ v.each do |t|
1008
+ if t.visible?(context)
1009
+ if visible_t.nil?
1010
+ visible_t = t
1011
+ else
1012
+ raise DuplicateNamesError, "Found two visible type definitions for `#{k}`: #{visible_t.inspect}, #{t.inspect}"
1013
+ end
1014
+ end
1015
+ end
1016
+ visible_t
1017
+ else
1018
+ v
1019
+ end
1020
+ end
1021
+ visible_types
1001
1022
  end
1002
1023
 
1003
1024
  # @param type_name [String]
1004
1025
  # @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]
1026
+ def get_type(type_name, context = GraphQL::Query::NullContext)
1027
+ local_entry = own_types[type_name]
1028
+ type_defn = case local_entry
1029
+ when nil
1030
+ nil
1031
+ when Array
1032
+ visible_t = nil
1033
+ warden = Warden.from_context(context)
1034
+ local_entry.each do |t|
1035
+ if warden.visible_type?(t, context)
1036
+ if visible_t.nil?
1037
+ visible_t = t
1038
+ else
1039
+ raise DuplicateNamesError, "Found two visible type definitions for `#{type_name}`: #{visible_t.inspect}, #{t.inspect}"
1040
+ end
1041
+ end
1042
+ end
1043
+ visible_t
1044
+ when Module
1045
+ local_entry
1046
+ else
1047
+ raise "Invariant: unexpected own_types[#{type_name.inspect}]: #{local_entry.inspect}"
1048
+ end
1049
+
1050
+ type_defn ||
1051
+ introspection_system.types[type_name] || # todo context-specific introspection?
1052
+ (superclass.respond_to?(:get_type) ? superclass.get_type(type_name, context) : nil)
1009
1053
  end
1010
1054
 
1011
1055
  # @api private
@@ -1182,19 +1226,19 @@ module GraphQL
1182
1226
  GraphQL::Schema::TypeExpression.build_type(type_owner, ast_node)
1183
1227
  end
1184
1228
 
1185
- def get_field(type_or_name, field_name)
1229
+ def get_field(type_or_name, field_name, context = GraphQL::Query::NullContext)
1186
1230
  parent_type = case type_or_name
1187
1231
  when LateBoundType
1188
- get_type(type_or_name.name)
1232
+ get_type(type_or_name.name, context)
1189
1233
  when String
1190
- get_type(type_or_name)
1234
+ get_type(type_or_name, context)
1191
1235
  when Module
1192
1236
  type_or_name
1193
1237
  else
1194
1238
  raise ArgumentError, "unexpected field owner for #{field_name.inspect}: #{type_or_name.inspect} (#{type_or_name.class})"
1195
1239
  end
1196
1240
 
1197
- if parent_type.kind.fields? && (field = parent_type.get_field(field_name))
1241
+ if parent_type.kind.fields? && (field = parent_type.get_field(field_name, context))
1198
1242
  field
1199
1243
  elsif parent_type == query && (entry_point_field = introspection_system.entry_point(name: field_name))
1200
1244
  entry_point_field
@@ -1205,8 +1249,8 @@ module GraphQL
1205
1249
  end
1206
1250
  end
1207
1251
 
1208
- def get_fields(type)
1209
- type.fields
1252
+ def get_fields(type, context = GraphQL::Query::NullContext)
1253
+ type.fields(context)
1210
1254
  end
1211
1255
 
1212
1256
  def introspection(new_introspection_namespace = nil)
@@ -1405,7 +1449,6 @@ module GraphQL
1405
1449
  if new_orphan_types.any?
1406
1450
  new_orphan_types = new_orphan_types.flatten
1407
1451
  add_type_and_traverse(new_orphan_types, root: false)
1408
- @orphan_types = new_orphan_types
1409
1452
  own_orphan_types.concat(new_orphan_types.flatten)
1410
1453
  end
1411
1454
 
@@ -1715,7 +1758,7 @@ module GraphQL
1715
1758
  if subscription.singleton_class.ancestors.include?(Subscriptions::SubscriptionRoot)
1716
1759
  GraphQL::Deprecation.warn("`extend Subscriptions::SubscriptionRoot` is no longer required; you may remove it from #{self}'s `subscription` root type (#{subscription}).")
1717
1760
  else
1718
- subscription.fields.each do |name, field|
1761
+ subscription.all_field_definitions.each do |field|
1719
1762
  field.extension(Subscriptions::DefaultSubscriptionResolveExtension)
1720
1763
  end
1721
1764
  end
@@ -1737,7 +1780,36 @@ module GraphQL
1737
1780
  end
1738
1781
  new_types = Array(t)
1739
1782
  addition = Schema::Addition.new(schema: self, own_types: own_types, new_types: new_types)
1740
- own_types.merge!(addition.types)
1783
+ addition.types.each do |name, types_entry| # rubocop:disable Development/ContextIsPassedCop -- build-time, not query-time
1784
+ if (prev_entry = own_types[name])
1785
+ prev_entries = case prev_entry
1786
+ when Array
1787
+ prev_entry
1788
+ when Module
1789
+ own_types[name] = [prev_entry]
1790
+ else
1791
+ raise "Invariant: unexpected prev_entry at #{name.inspect} when adding #{t.inspect}"
1792
+ end
1793
+
1794
+ case types_entry
1795
+ when Array
1796
+ prev_entries.concat(types_entry)
1797
+ prev_entries.uniq! # in case any are being re-visited
1798
+ when Module
1799
+ if !prev_entries.include?(types_entry)
1800
+ prev_entries << types_entry
1801
+ end
1802
+ else
1803
+ raise "Invariant: unexpected types_entry at #{name} when adding #{t.inspect}"
1804
+ end
1805
+ else
1806
+ if types_entry.is_a?(Array)
1807
+ types_entry.uniq!
1808
+ end
1809
+ own_types[name] = types_entry
1810
+ end
1811
+ end
1812
+
1741
1813
  own_possible_types.merge!(addition.possible_types) { |key, old_val, new_val| old_val + new_val }
1742
1814
  own_union_memberships.merge!(addition.union_memberships)
1743
1815
 
@@ -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
@@ -32,8 +32,10 @@ module GraphQL
32
32
 
33
33
  private
34
34
 
35
+ attr_reader :nodes
36
+
35
37
  def locations
36
- @nodes.map do |node|
38
+ nodes.map do |node|
37
39
  h = {"line" => node.line, "column" => node.col}
38
40
  h["filename"] = node.filename if node.filename
39
41
  h
@@ -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)
@@ -10,6 +10,7 @@ module GraphQL
10
10
  #
11
11
  # Original Algorithm: https://github.com/graphql/graphql-js/blob/master/src/validation/rules/OverlappingFieldsCanBeMerged.js
12
12
  NO_ARGS = {}.freeze
13
+
13
14
  Field = Struct.new(:node, :definition, :owner_type, :parents)
14
15
  FragmentSpread = Struct.new(:name, :parents)
15
16
 
@@ -17,20 +18,43 @@ module GraphQL
17
18
  super
18
19
  @visited_fragments = {}
19
20
  @compared_fragments = {}
21
+ @conflict_count = 0
20
22
  end
21
23
 
22
24
  def on_operation_definition(node, _parent)
23
- conflicts_within_selection_set(node, type_definition)
25
+ setting_errors { conflicts_within_selection_set(node, type_definition) }
24
26
  super
25
27
  end
26
28
 
27
29
  def on_field(node, _parent)
28
- conflicts_within_selection_set(node, type_definition)
30
+ setting_errors { conflicts_within_selection_set(node, type_definition) }
29
31
  super
30
32
  end
31
33
 
32
34
  private
33
35
 
36
+ def field_conflicts
37
+ @field_conflicts ||= Hash.new do |errors, field|
38
+ errors[field] = GraphQL::StaticValidation::FieldsWillMergeError.new(kind: :field, field_name: field)
39
+ end
40
+ end
41
+
42
+ def arg_conflicts
43
+ @arg_conflicts ||= Hash.new do |errors, field|
44
+ errors[field] = GraphQL::StaticValidation::FieldsWillMergeError.new(kind: :argument, field_name: field)
45
+ end
46
+ end
47
+
48
+ def setting_errors
49
+ @field_conflicts = nil
50
+ @arg_conflicts = nil
51
+
52
+ yield
53
+
54
+ field_conflicts.each_value { |error| add_error(error) }
55
+ arg_conflicts.each_value { |error| add_error(error) }
56
+ end
57
+
34
58
  def conflicts_within_selection_set(node, parent_type)
35
59
  return if parent_type.nil?
36
60
 
@@ -183,6 +207,8 @@ module GraphQL
183
207
  end
184
208
 
185
209
  def find_conflict(response_key, field1, field2, mutually_exclusive: false)
210
+ return if @conflict_count >= context.max_errors
211
+
186
212
  node1 = field1.node
187
213
  node2 = field2.node
188
214
 
@@ -191,28 +217,21 @@ module GraphQL
191
217
 
192
218
  if !are_mutually_exclusive
193
219
  if node1.name != node2.name
194
- errored_nodes = [node1.name, node2.name].sort.join(" or ")
195
- msg = "Field '#{response_key}' has a field conflict: #{errored_nodes}?"
196
- add_error(GraphQL::StaticValidation::FieldsWillMergeError.new(
197
- msg,
198
- nodes: [node1, node2],
199
- path: [],
200
- field_name: response_key,
201
- conflicts: errored_nodes
202
- ))
220
+ conflict = field_conflicts[response_key]
221
+
222
+ conflict.add_conflict(node1, node1.name)
223
+ conflict.add_conflict(node2, node2.name)
224
+
225
+ @conflict_count += 1
203
226
  end
204
227
 
205
228
  if !same_arguments?(node1, node2)
206
- args = [serialize_field_args(node1), serialize_field_args(node2)]
207
- conflicts = args.map { |arg| GraphQL::Language.serialize(arg) }.join(" or ")
208
- msg = "Field '#{response_key}' has an argument conflict: #{conflicts}?"
209
- add_error(GraphQL::StaticValidation::FieldsWillMergeError.new(
210
- msg,
211
- nodes: [node1, node2],
212
- path: [],
213
- field_name: response_key,
214
- conflicts: conflicts
215
- ))
229
+ conflict = arg_conflicts[response_key]
230
+
231
+ conflict.add_conflict(node1, GraphQL::Language.serialize(serialize_field_args(node1)))
232
+ conflict.add_conflict(node2, GraphQL::Language.serialize(serialize_field_args(node2)))
233
+
234
+ @conflict_count += 1
216
235
  end
217
236
  end
218
237
 
@@ -314,7 +333,7 @@ module GraphQL
314
333
  selections.each do |node|
315
334
  case node
316
335
  when GraphQL::Language::Nodes::Field
317
- definition = context.schema.get_field(owner_type, node.name)
336
+ definition = context.query.get_field(owner_type, node.name)
318
337
  fields << Field.new(node, definition, owner_type, parents)
319
338
  when GraphQL::Language::Nodes::InlineFragment
320
339
  fragment_type = node.type ? context.warden.get_type(node.type.name) : owner_type
@@ -3,12 +3,33 @@ module GraphQL
3
3
  module StaticValidation
4
4
  class FieldsWillMergeError < StaticValidation::Error
5
5
  attr_reader :field_name
6
- attr_reader :conflicts
6
+ attr_reader :kind
7
+
8
+ def initialize(kind:, field_name:)
9
+ super(nil)
7
10
 
8
- def initialize(message, path: nil, nodes: [], field_name:, conflicts:)
9
- super(message, path: path, nodes: nodes)
10
11
  @field_name = field_name
11
- @conflicts = conflicts
12
+ @kind = kind
13
+ @conflicts = []
14
+ end
15
+
16
+ def message
17
+ "Field '#{field_name}' has #{kind == :argument ? 'an' : 'a'} #{kind} conflict: #{conflicts}?"
18
+ end
19
+
20
+ def path
21
+ []
22
+ end
23
+
24
+ def conflicts
25
+ @conflicts.join(' or ')
26
+ end
27
+
28
+ def add_conflict(node, conflict_str)
29
+ return if nodes.include?(node)
30
+
31
+ @nodes << node
32
+ @conflicts << conflict_str
12
33
  end
13
34
 
14
35
  # A hash representation of this Message
@@ -17,7 +17,7 @@ module GraphQL
17
17
 
18
18
  def assert_required_args(ast_node, defn)
19
19
  present_argument_names = ast_node.arguments.map(&:name)
20
- required_argument_names = defn.arguments.each_value
20
+ required_argument_names = context.warden.arguments(defn)
21
21
  .select { |a| a.type.kind.non_null? && !a.default_value? && context.warden.get_argument(defn, a.name) }
22
22
  .map(&:name)
23
23
 
@@ -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
@@ -15,7 +15,8 @@ module GraphQL
15
15
  extend Forwardable
16
16
 
17
17
  attr_reader :query, :errors, :visitor,
18
- :on_dependency_resolve_handlers
18
+ :on_dependency_resolve_handlers,
19
+ :max_errors
19
20
 
20
21
  def_delegators :@query, :schema, :document, :fragments, :operations, :warden
21
22
 
@@ -1,10 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
3
  class StringEncodingError < GraphQL::RuntimeTypeError
4
- attr_reader :string
5
- def initialize(str)
4
+ attr_reader :string, :field, :path
5
+ def initialize(str, context:)
6
6
  @string = str
7
- super("String \"#{str}\" was encoded as #{str.encoding}! GraphQL requires an encoding compatible with UTF-8.")
7
+ @field = context[:current_field]
8
+ @path = context[:current_path]
9
+ message = "String #{str.inspect} was encoded as #{str.encoding}".dup
10
+ if @path
11
+ message << " @ #{@path.join(".")}"
12
+ end
13
+ if @field
14
+ message << " (#{@field.path})"
15
+ end
16
+ message << ". GraphQL requires an encoding compatible with UTF-8."
17
+ super(message)
8
18
  end
9
19
  end
10
20
  end
@@ -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
@@ -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
 
@@ -53,7 +61,37 @@ module GraphQL
53
61
 
54
62
  class << self
55
63
  private
56
- def stringify_args(arg_owner, args)
64
+
65
+ # This method does not support cyclic references in the Hash,
66
+ # nor does it support Hashes whose keys are not sortable
67
+ # with respect to their peers ( cases where a <=> b might throw an error )
68
+ def deep_sort_hash_keys(hash_to_sort)
69
+ raise ArgumentError.new("Argument must be a Hash") unless hash_to_sort.is_a?(Hash)
70
+ hash_to_sort.keys.sort.map do |k|
71
+ if hash_to_sort[k].is_a?(Hash)
72
+ [k, deep_sort_hash_keys(hash_to_sort[k])]
73
+ elsif hash_to_sort[k].is_a?(Array)
74
+ [k, deep_sort_array_hashes(hash_to_sort[k])]
75
+ else
76
+ [k, hash_to_sort[k]]
77
+ end
78
+ end.to_h
79
+ end
80
+
81
+ def deep_sort_array_hashes(array_to_inspect)
82
+ raise ArgumentError.new("Argument must be an Array") unless array_to_inspect.is_a?(Array)
83
+ array_to_inspect.map do |v|
84
+ if v.is_a?(Hash)
85
+ deep_sort_hash_keys(v)
86
+ elsif v.is_a?(Array)
87
+ deep_sort_array_hashes(v)
88
+ else
89
+ v
90
+ end
91
+ end
92
+ end
93
+
94
+ def stringify_args(arg_owner, args, context)
57
95
  arg_owner = arg_owner.respond_to?(:unwrap) ? arg_owner.unwrap : arg_owner # remove list and non-null wrappers
58
96
  case args
59
97
  when Hash
@@ -61,30 +99,44 @@ module GraphQL
61
99
  args.each do |k, v|
62
100
  arg_name = k.to_s
63
101
  camelized_arg_name = GraphQL::Schema::Member::BuildType.camelize(arg_name)
64
- arg_defn = get_arg_definition(arg_owner, camelized_arg_name)
102
+ arg_defn = get_arg_definition(arg_owner, camelized_arg_name, context)
65
103
 
66
104
  if arg_defn
67
105
  normalized_arg_name = camelized_arg_name
68
- next_args[normalized_arg_name] = stringify_args(arg_defn.type, v)
69
106
  else
70
107
  normalized_arg_name = arg_name
71
- arg_defn = get_arg_definition(arg_owner, normalized_arg_name)
108
+ arg_defn = get_arg_definition(arg_owner, normalized_arg_name, context)
109
+ end
110
+ arg_base_type = arg_defn.type.unwrap
111
+ # In the case where the value being emitted is seen as a "JSON"
112
+ # type, treat the value as one atomic unit of serialization
113
+ is_json_definition = arg_base_type && arg_base_type <= GraphQL::Types::JSON
114
+ if is_json_definition
115
+ sorted_value = if v.is_a?(Hash)
116
+ deep_sort_hash_keys(v)
117
+ elsif v.is_a?(Array)
118
+ deep_sort_array_hashes(v)
119
+ else
120
+ v
121
+ end
122
+ next_args[normalized_arg_name] = sorted_value.respond_to?(:to_json) ? sorted_value.to_json : sorted_value
123
+ else
124
+ next_args[normalized_arg_name] = stringify_args(arg_base_type, v, context)
72
125
  end
73
- next_args[normalized_arg_name] = stringify_args(arg_defn.type, v)
74
126
  end
75
127
  # Make sure they're deeply sorted
76
128
  next_args.sort.to_h
77
129
  when Array
78
- args.map { |a| stringify_args(arg_owner, a) }
130
+ args.map { |a| stringify_args(arg_owner, a, context) }
79
131
  when GraphQL::Schema::InputObject
80
- stringify_args(arg_owner, args.to_h)
132
+ stringify_args(arg_owner, args.to_h, context)
81
133
  else
82
134
  args
83
135
  end
84
136
  end
85
137
 
86
- def get_arg_definition(arg_owner, arg_name)
87
- 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 }
88
140
  end
89
141
  end
90
142
  end