graphql 1.4.5 → 1.5.3
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.
- checksums.yaml +4 -4
- data/lib/generators/graphql/enum_generator.rb +33 -0
- data/lib/generators/graphql/function_generator.rb +15 -0
- data/lib/generators/graphql/install_generator.rb +118 -0
- data/lib/generators/graphql/interface_generator.rb +27 -0
- data/lib/generators/graphql/loader_generator.rb +17 -0
- data/lib/generators/graphql/mutation_generator.rb +19 -0
- data/lib/generators/graphql/object_generator.rb +34 -0
- data/lib/generators/graphql/templates/enum.erb +4 -0
- data/lib/generators/graphql/templates/function.erb +17 -0
- data/lib/generators/graphql/templates/graphql_controller.erb +32 -0
- data/lib/generators/graphql/templates/interface.erb +4 -0
- data/lib/generators/graphql/templates/loader.erb +15 -0
- data/lib/generators/graphql/templates/mutation.erb +12 -0
- data/lib/generators/graphql/templates/object.erb +5 -0
- data/lib/generators/graphql/templates/query_type.erb +15 -0
- data/lib/generators/graphql/templates/schema.erb +34 -0
- data/lib/generators/graphql/templates/union.erb +4 -0
- data/lib/generators/graphql/type_generator.rb +78 -0
- data/lib/generators/graphql/union_generator.rb +33 -0
- data/lib/graphql.rb +10 -0
- data/lib/graphql/analysis/analyze_query.rb +1 -1
- data/lib/graphql/analysis/query_complexity.rb +6 -50
- data/lib/graphql/analysis/query_depth.rb +1 -1
- data/lib/graphql/argument.rb +21 -0
- data/lib/graphql/compatibility/execution_specification/counter_schema.rb +3 -3
- data/lib/graphql/define.rb +1 -0
- data/lib/graphql/define/assign_argument.rb +3 -19
- data/lib/graphql/define/assign_mutation_function.rb +34 -0
- data/lib/graphql/define/assign_object_field.rb +26 -14
- data/lib/graphql/define/defined_object_proxy.rb +21 -0
- data/lib/graphql/define/instance_definable.rb +61 -11
- data/lib/graphql/directive.rb +6 -1
- data/lib/graphql/execution/directive_checks.rb +1 -0
- data/lib/graphql/execution/execute.rb +14 -9
- data/lib/graphql/execution/field_result.rb +1 -0
- data/lib/graphql/execution/lazy.rb +8 -17
- data/lib/graphql/execution/lazy/lazy_method_map.rb +2 -0
- data/lib/graphql/execution/lazy/resolve.rb +1 -0
- data/lib/graphql/execution/selection_result.rb +1 -0
- data/lib/graphql/execution/typecast.rb +39 -26
- data/lib/graphql/field.rb +15 -3
- data/lib/graphql/field/resolve.rb +3 -3
- data/lib/graphql/function.rb +134 -0
- data/lib/graphql/id_type.rb +1 -1
- data/lib/graphql/input_object_type.rb +1 -1
- data/lib/graphql/internal_representation.rb +1 -1
- data/lib/graphql/internal_representation/node.rb +35 -107
- data/lib/graphql/internal_representation/rewrite.rb +189 -183
- data/lib/graphql/internal_representation/visit.rb +38 -0
- data/lib/graphql/introspection/input_value_type.rb +10 -1
- data/lib/graphql/introspection/schema_type.rb +1 -1
- data/lib/graphql/language/lexer.rb +6 -3
- data/lib/graphql/language/lexer.rl +6 -3
- data/lib/graphql/object_type.rb +53 -13
- data/lib/graphql/query.rb +30 -14
- data/lib/graphql/query/arguments.rb +2 -0
- data/lib/graphql/query/context.rb +2 -2
- data/lib/graphql/query/literal_input.rb +9 -0
- data/lib/graphql/query/serial_execution/field_resolution.rb +2 -2
- data/lib/graphql/query/serial_execution/selection_resolution.rb +1 -1
- data/lib/graphql/relay.rb +1 -0
- data/lib/graphql/relay/array_connection.rb +1 -1
- data/lib/graphql/relay/base_connection.rb +34 -15
- data/lib/graphql/relay/connection_resolve.rb +7 -2
- data/lib/graphql/relay/mutation.rb +45 -4
- data/lib/graphql/relay/node.rb +18 -6
- data/lib/graphql/relay/range_add.rb +45 -0
- data/lib/graphql/relay/relation_connection.rb +17 -2
- data/lib/graphql/runtime_type_error.rb +1 -0
- data/lib/graphql/schema.rb +40 -5
- data/lib/graphql/schema/base_64_encoder.rb +1 -0
- data/lib/graphql/schema/build_from_definition.rb +56 -21
- data/lib/graphql/schema/default_parse_error.rb +10 -0
- data/lib/graphql/schema/loader.rb +8 -1
- data/lib/graphql/schema/null_mask.rb +1 -0
- data/lib/graphql/schema/validation.rb +35 -0
- data/lib/graphql/static_validation.rb +1 -0
- data/lib/graphql/static_validation/all_rules.rb +1 -0
- data/lib/graphql/static_validation/arguments_validator.rb +7 -4
- data/lib/graphql/static_validation/definition_dependencies.rb +183 -0
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +28 -96
- data/lib/graphql/static_validation/rules/fragment_names_are_unique.rb +23 -0
- data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +8 -5
- data/lib/graphql/static_validation/rules/fragments_are_finite.rb +6 -31
- data/lib/graphql/static_validation/rules/fragments_are_used.rb +11 -41
- data/lib/graphql/static_validation/rules/operation_names_are_valid.rb +2 -2
- data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +19 -7
- data/lib/graphql/static_validation/validation_context.rb +22 -1
- data/lib/graphql/static_validation/validator.rb +4 -1
- data/lib/graphql/string_type.rb +5 -1
- data/lib/graphql/version.rb +1 -1
- data/readme.md +12 -3
- data/spec/generators/graphql/enum_generator_spec.rb +29 -0
- data/spec/generators/graphql/function_generator_spec.rb +33 -0
- data/spec/generators/graphql/install_generator_spec.rb +185 -0
- data/spec/generators/graphql/interface_generator_spec.rb +32 -0
- data/spec/generators/graphql/loader_generator_spec.rb +31 -0
- data/spec/generators/graphql/mutation_generator_spec.rb +28 -0
- data/spec/generators/graphql/object_generator_spec.rb +42 -0
- data/spec/generators/graphql/union_generator_spec.rb +50 -0
- data/spec/graphql/analysis/query_complexity_spec.rb +2 -1
- data/spec/graphql/define/instance_definable_spec.rb +38 -0
- data/spec/graphql/directive/skip_directive_spec.rb +1 -0
- data/spec/graphql/directive_spec.rb +18 -0
- data/spec/graphql/execution/typecast_spec.rb +41 -46
- data/spec/graphql/field_spec.rb +1 -1
- data/spec/graphql/function_spec.rb +128 -0
- data/spec/graphql/internal_representation/rewrite_spec.rb +166 -129
- data/spec/graphql/introspection/type_type_spec.rb +1 -1
- data/spec/graphql/language/lexer_spec.rb +6 -0
- data/spec/graphql/object_type_spec.rb +73 -2
- data/spec/graphql/query/arguments_spec.rb +28 -0
- data/spec/graphql/query/variables_spec.rb +7 -1
- data/spec/graphql/query_spec.rb +30 -0
- data/spec/graphql/relay/base_connection_spec.rb +26 -8
- data/spec/graphql/relay/connection_resolve_spec.rb +45 -0
- data/spec/graphql/relay/connection_type_spec.rb +21 -0
- data/spec/graphql/relay/node_spec.rb +30 -2
- data/spec/graphql/relay/range_add_spec.rb +113 -0
- data/spec/graphql/schema/build_from_definition_spec.rb +114 -0
- data/spec/graphql/schema/loader_spec.rb +1 -0
- data/spec/graphql/schema/printer_spec.rb +2 -2
- data/spec/graphql/schema/validation_spec.rb +80 -11
- data/spec/graphql/schema/warden_spec.rb +10 -10
- data/spec/graphql/schema_spec.rb +18 -1
- data/spec/graphql/static_validation/rules/argument_literals_are_compatible_spec.rb +16 -0
- data/spec/graphql/static_validation/rules/fields_will_merge_spec.rb +50 -3
- data/spec/graphql/static_validation/rules/fragment_names_are_unique_spec.rb +27 -0
- data/spec/graphql/static_validation/rules/fragments_are_finite_spec.rb +57 -0
- data/spec/graphql/static_validation/rules/fragments_are_used_spec.rb +1 -1
- data/spec/graphql/static_validation/rules/variables_are_input_types_spec.rb +14 -0
- data/spec/graphql/string_type_spec.rb +7 -0
- data/spec/spec_helper.rb +3 -3
- data/spec/support/base_generator_test.rb +7 -0
- data/spec/support/dummy/schema.rb +32 -30
- data/spec/support/star_wars/schema.rb +81 -23
- metadata +98 -20
- data/lib/graphql/internal_representation/selection.rb +0 -85
@@ -107,7 +107,14 @@ module GraphQL
|
|
107
107
|
)
|
108
108
|
when "ARGUMENT"
|
109
109
|
kwargs = {}
|
110
|
-
|
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"],
|
@@ -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
|
-
|
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::
|
18
|
+
if !parent_defn.is_a?(GraphQL::InputObjectType)
|
18
19
|
return
|
19
20
|
end
|
20
21
|
end
|
21
|
-
|
22
|
+
when GraphQL::Language::Nodes::Directive
|
22
23
|
parent_defn = context.schema.directives[parent.name]
|
23
|
-
|
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
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
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
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
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
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
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
|