graphql 1.4.5 → 1.5.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (139) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/enum_generator.rb +33 -0
  3. data/lib/generators/graphql/function_generator.rb +15 -0
  4. data/lib/generators/graphql/install_generator.rb +118 -0
  5. data/lib/generators/graphql/interface_generator.rb +27 -0
  6. data/lib/generators/graphql/loader_generator.rb +17 -0
  7. data/lib/generators/graphql/mutation_generator.rb +19 -0
  8. data/lib/generators/graphql/object_generator.rb +34 -0
  9. data/lib/generators/graphql/templates/enum.erb +4 -0
  10. data/lib/generators/graphql/templates/function.erb +17 -0
  11. data/lib/generators/graphql/templates/graphql_controller.erb +32 -0
  12. data/lib/generators/graphql/templates/interface.erb +4 -0
  13. data/lib/generators/graphql/templates/loader.erb +15 -0
  14. data/lib/generators/graphql/templates/mutation.erb +12 -0
  15. data/lib/generators/graphql/templates/object.erb +5 -0
  16. data/lib/generators/graphql/templates/query_type.erb +15 -0
  17. data/lib/generators/graphql/templates/schema.erb +34 -0
  18. data/lib/generators/graphql/templates/union.erb +4 -0
  19. data/lib/generators/graphql/type_generator.rb +78 -0
  20. data/lib/generators/graphql/union_generator.rb +33 -0
  21. data/lib/graphql.rb +10 -0
  22. data/lib/graphql/analysis/analyze_query.rb +1 -1
  23. data/lib/graphql/analysis/query_complexity.rb +6 -50
  24. data/lib/graphql/analysis/query_depth.rb +1 -1
  25. data/lib/graphql/argument.rb +21 -0
  26. data/lib/graphql/compatibility/execution_specification/counter_schema.rb +3 -3
  27. data/lib/graphql/define.rb +1 -0
  28. data/lib/graphql/define/assign_argument.rb +3 -19
  29. data/lib/graphql/define/assign_mutation_function.rb +34 -0
  30. data/lib/graphql/define/assign_object_field.rb +26 -14
  31. data/lib/graphql/define/defined_object_proxy.rb +21 -0
  32. data/lib/graphql/define/instance_definable.rb +61 -11
  33. data/lib/graphql/directive.rb +6 -1
  34. data/lib/graphql/execution/directive_checks.rb +1 -0
  35. data/lib/graphql/execution/execute.rb +14 -9
  36. data/lib/graphql/execution/field_result.rb +1 -0
  37. data/lib/graphql/execution/lazy.rb +8 -17
  38. data/lib/graphql/execution/lazy/lazy_method_map.rb +2 -0
  39. data/lib/graphql/execution/lazy/resolve.rb +1 -0
  40. data/lib/graphql/execution/selection_result.rb +1 -0
  41. data/lib/graphql/execution/typecast.rb +39 -26
  42. data/lib/graphql/field.rb +15 -3
  43. data/lib/graphql/field/resolve.rb +3 -3
  44. data/lib/graphql/function.rb +134 -0
  45. data/lib/graphql/id_type.rb +1 -1
  46. data/lib/graphql/input_object_type.rb +1 -1
  47. data/lib/graphql/internal_representation.rb +1 -1
  48. data/lib/graphql/internal_representation/node.rb +35 -107
  49. data/lib/graphql/internal_representation/rewrite.rb +189 -183
  50. data/lib/graphql/internal_representation/visit.rb +38 -0
  51. data/lib/graphql/introspection/input_value_type.rb +10 -1
  52. data/lib/graphql/introspection/schema_type.rb +1 -1
  53. data/lib/graphql/language/lexer.rb +6 -3
  54. data/lib/graphql/language/lexer.rl +6 -3
  55. data/lib/graphql/object_type.rb +53 -13
  56. data/lib/graphql/query.rb +30 -14
  57. data/lib/graphql/query/arguments.rb +2 -0
  58. data/lib/graphql/query/context.rb +2 -2
  59. data/lib/graphql/query/literal_input.rb +9 -0
  60. data/lib/graphql/query/serial_execution/field_resolution.rb +2 -2
  61. data/lib/graphql/query/serial_execution/selection_resolution.rb +1 -1
  62. data/lib/graphql/relay.rb +1 -0
  63. data/lib/graphql/relay/array_connection.rb +1 -1
  64. data/lib/graphql/relay/base_connection.rb +34 -15
  65. data/lib/graphql/relay/connection_resolve.rb +7 -2
  66. data/lib/graphql/relay/mutation.rb +45 -4
  67. data/lib/graphql/relay/node.rb +18 -6
  68. data/lib/graphql/relay/range_add.rb +45 -0
  69. data/lib/graphql/relay/relation_connection.rb +17 -2
  70. data/lib/graphql/runtime_type_error.rb +1 -0
  71. data/lib/graphql/schema.rb +40 -5
  72. data/lib/graphql/schema/base_64_encoder.rb +1 -0
  73. data/lib/graphql/schema/build_from_definition.rb +56 -21
  74. data/lib/graphql/schema/default_parse_error.rb +10 -0
  75. data/lib/graphql/schema/loader.rb +8 -1
  76. data/lib/graphql/schema/null_mask.rb +1 -0
  77. data/lib/graphql/schema/validation.rb +35 -0
  78. data/lib/graphql/static_validation.rb +1 -0
  79. data/lib/graphql/static_validation/all_rules.rb +1 -0
  80. data/lib/graphql/static_validation/arguments_validator.rb +7 -4
  81. data/lib/graphql/static_validation/definition_dependencies.rb +183 -0
  82. data/lib/graphql/static_validation/rules/fields_will_merge.rb +28 -96
  83. data/lib/graphql/static_validation/rules/fragment_names_are_unique.rb +23 -0
  84. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +8 -5
  85. data/lib/graphql/static_validation/rules/fragments_are_finite.rb +6 -31
  86. data/lib/graphql/static_validation/rules/fragments_are_used.rb +11 -41
  87. data/lib/graphql/static_validation/rules/operation_names_are_valid.rb +2 -2
  88. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +19 -7
  89. data/lib/graphql/static_validation/validation_context.rb +22 -1
  90. data/lib/graphql/static_validation/validator.rb +4 -1
  91. data/lib/graphql/string_type.rb +5 -1
  92. data/lib/graphql/version.rb +1 -1
  93. data/readme.md +12 -3
  94. data/spec/generators/graphql/enum_generator_spec.rb +29 -0
  95. data/spec/generators/graphql/function_generator_spec.rb +33 -0
  96. data/spec/generators/graphql/install_generator_spec.rb +185 -0
  97. data/spec/generators/graphql/interface_generator_spec.rb +32 -0
  98. data/spec/generators/graphql/loader_generator_spec.rb +31 -0
  99. data/spec/generators/graphql/mutation_generator_spec.rb +28 -0
  100. data/spec/generators/graphql/object_generator_spec.rb +42 -0
  101. data/spec/generators/graphql/union_generator_spec.rb +50 -0
  102. data/spec/graphql/analysis/query_complexity_spec.rb +2 -1
  103. data/spec/graphql/define/instance_definable_spec.rb +38 -0
  104. data/spec/graphql/directive/skip_directive_spec.rb +1 -0
  105. data/spec/graphql/directive_spec.rb +18 -0
  106. data/spec/graphql/execution/typecast_spec.rb +41 -46
  107. data/spec/graphql/field_spec.rb +1 -1
  108. data/spec/graphql/function_spec.rb +128 -0
  109. data/spec/graphql/internal_representation/rewrite_spec.rb +166 -129
  110. data/spec/graphql/introspection/type_type_spec.rb +1 -1
  111. data/spec/graphql/language/lexer_spec.rb +6 -0
  112. data/spec/graphql/object_type_spec.rb +73 -2
  113. data/spec/graphql/query/arguments_spec.rb +28 -0
  114. data/spec/graphql/query/variables_spec.rb +7 -1
  115. data/spec/graphql/query_spec.rb +30 -0
  116. data/spec/graphql/relay/base_connection_spec.rb +26 -8
  117. data/spec/graphql/relay/connection_resolve_spec.rb +45 -0
  118. data/spec/graphql/relay/connection_type_spec.rb +21 -0
  119. data/spec/graphql/relay/node_spec.rb +30 -2
  120. data/spec/graphql/relay/range_add_spec.rb +113 -0
  121. data/spec/graphql/schema/build_from_definition_spec.rb +114 -0
  122. data/spec/graphql/schema/loader_spec.rb +1 -0
  123. data/spec/graphql/schema/printer_spec.rb +2 -2
  124. data/spec/graphql/schema/validation_spec.rb +80 -11
  125. data/spec/graphql/schema/warden_spec.rb +10 -10
  126. data/spec/graphql/schema_spec.rb +18 -1
  127. data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +16 -0
  128. data/spec/graphql/static_validation/rules/fields_will_merge_spec.rb +50 -3
  129. data/spec/graphql/static_validation/rules/fragment_names_are_unique_spec.rb +27 -0
  130. data/spec/graphql/static_validation/rules/fragments_are_finite_spec.rb +57 -0
  131. data/spec/graphql/static_validation/rules/fragments_are_used_spec.rb +1 -1
  132. data/spec/graphql/static_validation/rules/variables_are_input_types_spec.rb +14 -0
  133. data/spec/graphql/string_type_spec.rb +7 -0
  134. data/spec/spec_helper.rb +3 -3
  135. data/spec/support/base_generator_test.rb +7 -0
  136. data/spec/support/dummy/schema.rb +32 -30
  137. data/spec/support/star_wars/schema.rb +81 -23
  138. metadata +98 -20
  139. data/lib/graphql/internal_representation/selection.rb +0 -85
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ class Schema
4
+ module DefaultParseError
5
+ def self.call(parse_error, ctx)
6
+ ctx.errors.push(parse_error)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -107,7 +107,14 @@ module GraphQL
107
107
  )
108
108
  when "ARGUMENT"
109
109
  kwargs = {}
110
- kwargs[:default_value] = JSON.parse(type["defaultValue"], quirks_mode: true) if type["defaultValue"]
110
+ if type["defaultValue"]
111
+ kwargs[:default_value] = begin
112
+ JSON.parse(type["defaultValue"], quirks_mode: true)
113
+ rescue JSON::ParserError
114
+ # Enum values are not valid JSON, they're bare identifiers
115
+ type["default_value"]
116
+ end
117
+ end
111
118
 
112
119
  Argument.define(
113
120
  name: type["name"],
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module GraphQL
2
3
  class Schema
3
4
  # @api private
@@ -196,6 +196,40 @@ module GraphQL
196
196
  # no worries
197
197
  end
198
198
  }
199
+
200
+ INTERFACES_ARE_IMPLEMENTED = ->(obj_type) {
201
+ field_errors = []
202
+ obj_type.interfaces.each do |interface_type|
203
+ interface_type.fields.each do |field_name, field_defn|
204
+ object_field = obj_type.get_field(field_name)
205
+ if object_field.nil?
206
+ field_errors << %|"#{field_name}" is required by #{interface_type.name} but not implemented by #{obj_type.name}|
207
+ elsif !GraphQL::Execution::Typecast.subtype?(field_defn.type, object_field.type)
208
+ field_errors << %|"#{field_name}" is required by #{interface_type.name} to return #{field_defn.type} but #{obj_type.name}.#{field_name} returns #{object_field.type}|
209
+ else
210
+ field_defn.arguments.each do |arg_name, arg_defn|
211
+ object_field_arg = object_field.arguments[arg_name]
212
+ if object_field_arg.nil?
213
+ field_errors << %|"#{arg_name}" argument is required by #{interface_type.name}.#{field_name} but not accepted by #{obj_type.name}.#{field_name}|
214
+ elsif arg_defn.type != object_field_arg.type
215
+ field_errors << %|"#{arg_name}" is required by #{interface_type.name}.#{field_defn.name} to accept #{arg_defn.type} but #{obj_type.name}.#{field_name} accepts #{object_field_arg.type} for "#{arg_name}"|
216
+ end
217
+ end
218
+
219
+ object_field.arguments.each do |arg_name, arg_defn|
220
+ if field_defn.arguments[arg_name].nil? && arg_defn.type.is_a?(GraphQL::NonNullType)
221
+ field_errors << %|"#{arg_name}" is not accepted by #{interface_type.name}.#{field_name} but required by #{obj_type.name}.#{field_name}|
222
+ end
223
+ end
224
+ end
225
+ end
226
+ end
227
+ if field_errors.any?
228
+ "#{obj_type.name} failed to implement some interfaces: #{field_errors.join(", ")}"
229
+ else
230
+ nil
231
+ end
232
+ }
199
233
  end
200
234
 
201
235
  # A mapping of `{Class => [Proc, Proc...]}` pairs.
@@ -227,6 +261,7 @@ module GraphQL
227
261
  GraphQL::ObjectType => [
228
262
  Rules.assert_property_list_of(:interfaces, GraphQL::InterfaceType),
229
263
  Rules::FIELDS_ARE_VALID,
264
+ Rules::INTERFACES_ARE_IMPLEMENTED,
230
265
  ],
231
266
  GraphQL::InputObjectType => [
232
267
  Rules::ARGUMENTS_ARE_STRING_TO_ARGUMENT,
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
  require "graphql/static_validation/message"
3
3
  require "graphql/static_validation/arguments_validator"
4
+ require "graphql/static_validation/definition_dependencies"
4
5
  require "graphql/static_validation/type_stack"
5
6
  require "graphql/static_validation/validator"
6
7
  require "graphql/static_validation/validation_context"
@@ -12,6 +12,7 @@ module GraphQL
12
12
  GraphQL::StaticValidation::UniqueDirectivesPerLocation,
13
13
  GraphQL::StaticValidation::FragmentsAreFinite,
14
14
  GraphQL::StaticValidation::FragmentsAreNamed,
15
+ GraphQL::StaticValidation::FragmentNamesAreUnique,
15
16
  GraphQL::StaticValidation::FragmentsAreUsed,
16
17
  GraphQL::StaticValidation::FragmentTypesExist,
17
18
  GraphQL::StaticValidation::FragmentsAreOnCompositeTypes,
@@ -8,20 +8,23 @@ module GraphQL
8
8
  def validate(context)
9
9
  visitor = context.visitor
10
10
  visitor[GraphQL::Language::Nodes::Argument] << ->(node, parent) {
11
- if parent.is_a?(GraphQL::Language::Nodes::InputObject)
11
+ case parent
12
+ when GraphQL::Language::Nodes::InputObject
12
13
  arg_defn = context.argument_definition
13
14
  if arg_defn.nil?
14
15
  return
15
16
  else
16
17
  parent_defn = arg_defn.type.unwrap
17
- if parent_defn.is_a?(GraphQL::ScalarType)
18
+ if !parent_defn.is_a?(GraphQL::InputObjectType)
18
19
  return
19
20
  end
20
21
  end
21
- elsif parent.is_a?(GraphQL::Language::Nodes::Directive)
22
+ when GraphQL::Language::Nodes::Directive
22
23
  parent_defn = context.schema.directives[parent.name]
23
- else
24
+ when GraphQL::Language::Nodes::Field
24
25
  parent_defn = context.field_definition
26
+ else
27
+ raise "Unexpected argument parent: #{parent.class} (##{parent})"
25
28
  end
26
29
  validate_node(parent, node, parent_defn, context)
27
30
  }
@@ -0,0 +1,183 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module StaticValidation
4
+ # Track fragment dependencies for operations
5
+ # and expose the fragment definitions which
6
+ # are used by a given operation
7
+ class DefinitionDependencies
8
+ def self.mount(visitor)
9
+ deps = self.new
10
+ deps.mount(visitor)
11
+ deps
12
+ end
13
+
14
+ def initialize
15
+ # { name => node } pairs for fragments
16
+ @fragment_definitions = {}
17
+
18
+ # This tracks dependencies from fragment to Node where it was used
19
+ # { frag_name => [dependent_node, dependent_node]}
20
+ @dependent_definitions = Hash.new { |h, k| h[k] = Set.new }
21
+
22
+ # First-level usages of spreads within definitions
23
+ # (When a key has an empty list as its value,
24
+ # we can resolve that key's depenedents)
25
+ # { string => [node, node ...] }
26
+ @immediate_dependencies = Hash.new { |h, k| h[k] = Set.new }
27
+ end
28
+
29
+ # A map of operation definitions to an array of that operation's dependencies
30
+ # @return [DependencyMap]
31
+ def dependency_map(&block)
32
+ @dependency_map ||= resolve_dependencies(&block)
33
+ end
34
+
35
+
36
+ def mount(context)
37
+ visitor = context.visitor
38
+ # When we encounter a spread,
39
+ # this node is the one who depends on it
40
+ current_parent = nil
41
+
42
+ visitor[GraphQL::Language::Nodes::OperationDefinition] << ->(node, prev_node) {
43
+ current_parent = NodeWithPath.new(node, context.path)
44
+ }
45
+
46
+ visitor[GraphQL::Language::Nodes::OperationDefinition].leave << ->(node, prev_node) {
47
+ current_parent = nil
48
+ }
49
+
50
+ visitor[GraphQL::Language::Nodes::FragmentDefinition] << ->(node, prev_node) {
51
+ current_parent = @fragment_definitions[node.name] = NodeWithPath.new(node, context.path)
52
+ }
53
+
54
+ visitor[GraphQL::Language::Nodes::FragmentDefinition].leave << ->(node, prev_node) {
55
+ current_parent = nil
56
+ }
57
+
58
+ visitor[GraphQL::Language::Nodes::FragmentSpread] << ->(node, prev_node) {
59
+ # Track both sides of the dependency
60
+ @dependent_definitions[node.name] << current_parent
61
+ @immediate_dependencies[current_parent.name] << NodeWithPath.new(node, context.path)
62
+ }
63
+ end
64
+
65
+ # Map definition AST nodes to the definition AST nodes they depend on.
66
+ # Expose circular depednencies.
67
+ class DependencyMap
68
+ # @return [Array<GraphQL::Language::Nodes::FragmentDefinition>]
69
+ attr_reader :cyclical_definitions
70
+
71
+ # @return [Hash<Node, Array<GraphQL::Language::Nodes::FragmentSpread>>]
72
+ attr_reader :unmet_dependencies
73
+
74
+ # @return [Array<GraphQL::Language::Nodes::FragmentDefinition>]
75
+ attr_reader :unused_dependencies
76
+
77
+ def initialize
78
+ @dependencies = Hash.new { |h, k| h[k] = [] }
79
+ @cyclical_definitions = []
80
+ @unmet_dependencies = Hash.new { |h, k| h[k] = [] }
81
+ @unused_dependencies = []
82
+ end
83
+
84
+ # @return [Array<GraphQL::Language::Nodes::AbstractNode>] dependencies for `definition_node`
85
+ def [](definition_node)
86
+ @dependencies[definition_node]
87
+ end
88
+ end
89
+
90
+ class NodeWithPath
91
+ extend Forwardable
92
+ attr_reader :node, :path
93
+ def initialize(node, path)
94
+ @node = node
95
+ @path = path
96
+ end
97
+
98
+ def_delegators :@node, :name, :eql?, :hash
99
+ end
100
+
101
+ private
102
+
103
+ # Return a hash of { node => [node, node ... ]} pairs
104
+ # Keys are top-level definitions
105
+ # Values are arrays of flattened dependencies
106
+ def resolve_dependencies
107
+ dependency_map = DependencyMap.new
108
+ # Don't allow the loop to run more times
109
+ # than the number of fragments in the document
110
+ max_loops = @fragment_definitions.size
111
+ loops = 0
112
+
113
+ # Instead of tracking independent fragments _as you visit_,
114
+ # determine them at the end. This way, we can treat fragments with the
115
+ # same name as if they were the same name. If _any_ of the fragments
116
+ # with that name has a dependency, we record it.
117
+ independent_fragment_names = @fragment_definitions.each_key.select { |name| !@immediate_dependencies.key?(name) }
118
+
119
+ while fragment_name = independent_fragment_names.pop
120
+ loops += 1
121
+ if loops > max_loops
122
+ raise("Resolution loops exceeded the number of definitions; infinite loop detected.")
123
+ end
124
+ fragment_node = @fragment_definitions[fragment_name]
125
+ # Since it's independent, let's remove it from here.
126
+ # That way, we can use the remainder to identify cycles
127
+ @immediate_dependencies.delete(fragment_name)
128
+ fragment_usages = @dependent_definitions[fragment_name]
129
+ if fragment_usages.none?
130
+ # If we didn't record any usages during the visit,
131
+ # then this fragment is unused.
132
+ dependency_map.unused_dependencies << fragment_node
133
+ else
134
+ fragment_usages.each do |definition_node|
135
+ # Register the dependency AND second-order dependencies
136
+ dependency_map[definition_node] << fragment_node
137
+ dependency_map[definition_node].concat(dependency_map[fragment_node])
138
+ # Since we've regestered it, remove it from our to-do list
139
+ deps = @immediate_dependencies[definition_node.name]
140
+ # Can't find a way to _just_ delete from `deps` and return the deleted entries
141
+ removed, remaining = deps.partition { |spread| spread.name == fragment_name }
142
+ @immediate_dependencies[definition_node.name] = remaining
143
+ if block_given?
144
+ yield(definition_node, removed, fragment_node)
145
+ end
146
+ if remaining.none? && definition_node.node.is_a?(GraphQL::Language::Nodes::FragmentDefinition)
147
+ # If all of this definition's dependencies have
148
+ # been resolved, we can now resolve its
149
+ # own dependents.
150
+ independent_fragment_names << definition_node.name
151
+ end
152
+ end
153
+ end
154
+ end
155
+
156
+ # If any dependencies were _unmet_
157
+ # (eg, spreads with no corresponding definition)
158
+ # then they're still in there
159
+ @immediate_dependencies.each do |defn_name, deps|
160
+ deps.each do |spread|
161
+ if @fragment_definitions[spread.name].nil?
162
+ defn_node = @fragment_definitions[defn_name]
163
+ dependency_map.unmet_dependencies[defn_node] << spread
164
+ deps.delete(spread)
165
+ end
166
+ end
167
+ if deps.none?
168
+ @immediate_dependencies.delete(defn_name)
169
+ end
170
+ end
171
+
172
+ # Anything left in @immediate_dependencies is cyclical
173
+ cyclical_nodes = @immediate_dependencies.keys.map { |n| @fragment_definitions[n] }
174
+ # @immediate_dependencies also includes operation names, but we don't care about
175
+ # those. They became nil when we looked them up on `@fragment_definitions`, so remove them.
176
+ cyclical_nodes.compact!
177
+ dependency_map.cyclical_definitions.concat(cyclical_nodes)
178
+
179
+ dependency_map
180
+ end
181
+ end
182
+ end
183
+ end
@@ -3,105 +3,37 @@ module GraphQL
3
3
  module StaticValidation
4
4
  class FieldsWillMerge
5
5
  def validate(context)
6
- fragments = {}
7
- has_selections = []
8
- visitor = context.visitor
9
- visitor[GraphQL::Language::Nodes::OperationDefinition] << ->(node, parent) {
10
- if node.selections.any?
11
- has_selections << node
12
- end
13
- }
14
- visitor[GraphQL::Language::Nodes::Document].leave << ->(node, parent) {
15
- has_selections.each { |node|
16
- field_map = gather_fields_by_name(node.selections, {}, [], context)
17
- find_conflicts(field_map, [], context)
18
- }
19
- }
20
- end
21
-
22
- private
23
-
24
- def find_conflicts(field_map, visited_fragments, context)
25
- field_map.each do |name, ast_fields|
26
- comparison = FieldDefinitionComparison.new(name, ast_fields, context)
27
- context.errors.push(*comparison.errors)
28
-
29
-
30
- subfield_map = {}
31
- ast_fields.each do |defn|
32
- gather_fields_by_name(defn.selections, subfield_map, visited_fragments, context)
33
- end
34
-
35
- find_conflicts(subfield_map, visited_fragments, context)
36
- end
37
- end
38
-
39
- def gather_fields_by_name(fields, field_map, visited_fragments, context)
40
- fields.each do |field|
41
- case field
42
- when GraphQL::Language::Nodes::InlineFragment
43
- next_fields = field.selections
44
- when GraphQL::Language::Nodes::FragmentSpread
45
- if visited_fragments.include?(field.name)
46
- next
47
- else
48
- visited_fragments << field.name
6
+ context.each_irep_node do |node|
7
+ if node.ast_nodes.size > 1
8
+
9
+ # Check for more than one GraphQL::Field backing this node:
10
+ if node.definitions.size > 1
11
+ defn_names = node.definitions.map { |d| d.name }.sort.join(" or ")
12
+ msg = "Field '#{node.name}' has a field conflict: #{defn_names}?"
13
+ context.errors << GraphQL::StaticValidation::Message.new(msg, nodes: node.ast_nodes.to_a)
49
14
  end
50
- fragment_defn = context.fragments[field.name]
51
- next_fields = fragment_defn ? fragment_defn.selections : []
52
- when GraphQL::Language::Nodes::Field
53
- name_in_selection = field.alias || field.name
54
- field_map[name_in_selection] ||= []
55
- field_map[name_in_selection].push(field)
56
- next_fields = []
57
- else
58
- raise "Unexpected field for merging: #{field}"
59
- end
60
- gather_fields_by_name(next_fields, field_map, visited_fragments, context)
61
- end
62
- field_map
63
- end
64
-
65
- # Compare two field definitions, add errors to the list if there are any
66
- class FieldDefinitionComparison
67
- include GraphQL::StaticValidation::Message::MessageHelper
68
- NAMED_VALUES = [GraphQL::Language::Nodes::Enum, GraphQL::Language::Nodes::VariableIdentifier]
69
- attr_reader :errors
70
- def initialize(name, defs, context)
71
- errors = []
72
-
73
- names = defs.map(&:name).uniq
74
- if names.length != 1
75
- errors << message("Field '#{name}' has a field conflict: #{names.join(" or ")}?", defs.first, context: context)
76
- end
77
15
 
78
- args = defs.map { |defn| reduce_list(defn.arguments)}.uniq
79
- if args.length != 1
80
- errors << message("Field '#{name}' has an argument conflict: #{args.map{ |arg| GraphQL::Language.serialize(arg) }.join(" or ")}?", defs.first, context: context)
81
- end
82
-
83
- @errors = errors
84
- end
85
-
86
- private
87
-
88
- def print_arg(arg)
89
- case arg
90
- when GraphQL::Language::Nodes::VariableIdentifier
91
- "$#{arg.name}"
92
- when GraphQL::Language::Nodes::Enum
93
- "#{arg.name}"
94
- else
95
- GraphQL::Language.serialize(arg)
96
- end
97
- end
16
+ # Check for incompatible / non-identical arguments on this node:
17
+ args = node.ast_nodes.map do |n|
18
+ n.arguments.reduce({}) do |memo, a|
19
+ arg_value = a.value
20
+ memo[a.name] = case arg_value
21
+ when GraphQL::Language::Nodes::VariableIdentifier
22
+ "$#{arg_value.name}"
23
+ when GraphQL::Language::Nodes::Enum
24
+ "#{arg_value.name}"
25
+ else
26
+ GraphQL::Language.serialize(arg_value)
27
+ end
28
+ memo
29
+ end
30
+ end
31
+ args.uniq!
98
32
 
99
- # Turn AST tree into a hash
100
- # can't look up args, the names just have to match
101
- def reduce_list(args)
102
- args.reduce({}) do |memo, a|
103
- memo[a.name] = print_arg(a.value)
104
- memo
33
+ if args.length > 1
34
+ msg = "Field '#{node.name}' has an argument conflict: #{args.map{ |arg| GraphQL::Language.serialize(arg) }.join(" or ")}?"
35
+ context.errors << GraphQL::StaticValidation::Message.new(msg, nodes: node.ast_nodes.to_a)
36
+ end
105
37
  end
106
38
  end
107
39
  end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+ module GraphQL
3
+ module StaticValidation
4
+ class FragmentNamesAreUnique
5
+ include GraphQL::StaticValidation::Message::MessageHelper
6
+
7
+ def validate(context)
8
+ fragments_by_name = Hash.new { |h, k| h[k] = [] }
9
+ context.visitor[GraphQL::Language::Nodes::FragmentDefinition] << ->(node, parent) {
10
+ fragments_by_name[node.name] << node
11
+ }
12
+
13
+ context.visitor[GraphQL::Language::Nodes::Document].leave << ->(node, parent) {
14
+ fragments_by_name.each do |name, fragments|
15
+ if fragments.length > 1
16
+ context.errors << message(%|Fragment name "#{name}" must be unique|, fragments, context: context)
17
+ end
18
+ end
19
+ }
20
+ end
21
+ end
22
+ end
23
+ end