graphql 1.10.0.pre1 → 1.10.0.pre2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/generators/graphql/core.rb +1 -0
- data/lib/generators/graphql/install_generator.rb +1 -0
- data/lib/generators/graphql/mutation_generator.rb +1 -1
- data/lib/generators/graphql/templates/base_field.erb +0 -4
- data/lib/generators/graphql/templates/base_mutation.erb +8 -0
- data/lib/generators/graphql/templates/graphql_controller.erb +5 -0
- data/lib/generators/graphql/templates/mutation.erb +1 -1
- data/lib/generators/graphql/templates/schema.erb +1 -1
- data/lib/graphql.rb +4 -1
- data/lib/graphql/analysis/ast.rb +14 -13
- data/lib/graphql/analysis/ast/analyzer.rb +23 -4
- data/lib/graphql/analysis/ast/field_usage.rb +1 -1
- data/lib/graphql/analysis/ast/max_query_complexity.rb +3 -3
- data/lib/graphql/analysis/ast/max_query_depth.rb +7 -3
- data/lib/graphql/analysis/ast/query_complexity.rb +2 -2
- data/lib/graphql/analysis/ast/visitor.rb +3 -3
- data/lib/graphql/base_type.rb +1 -1
- data/lib/graphql/directive.rb +0 -1
- data/lib/graphql/directive/deprecated_directive.rb +1 -12
- data/lib/graphql/execution/errors.rb +4 -8
- data/lib/graphql/execution/interpreter.rb +5 -11
- data/lib/graphql/execution/interpreter/runtime.rb +56 -48
- data/lib/graphql/execution/lazy/lazy_method_map.rb +4 -0
- data/lib/graphql/execution/lookahead.rb +5 -5
- data/lib/graphql/execution/multiplex.rb +10 -0
- data/lib/graphql/function.rb +1 -1
- data/lib/graphql/input_object_type.rb +3 -2
- data/lib/graphql/interface_type.rb +1 -1
- data/lib/graphql/introspection/base_object.rb +2 -5
- data/lib/graphql/introspection/directive_type.rb +1 -1
- data/lib/graphql/introspection/entry_points.rb +6 -6
- data/lib/graphql/introspection/schema_type.rb +1 -6
- data/lib/graphql/introspection/type_type.rb +5 -5
- data/lib/graphql/language.rb +1 -1
- data/lib/graphql/language/block_string.rb +2 -2
- data/lib/graphql/language/definition_slice.rb +21 -10
- data/lib/graphql/language/document_from_schema_definition.rb +42 -42
- data/lib/graphql/language/lexer.rb +49 -48
- data/lib/graphql/language/lexer.rl +49 -48
- data/lib/graphql/language/nodes.rb +11 -8
- data/lib/graphql/language/parser.rb +4 -1
- data/lib/graphql/language/parser.y +4 -1
- data/lib/graphql/language/token.rb +1 -1
- data/lib/graphql/pagination/array_connection.rb +0 -1
- data/lib/graphql/pagination/connection.rb +31 -10
- data/lib/graphql/pagination/connections.rb +7 -2
- data/lib/graphql/pagination/relation_connection.rb +1 -7
- data/lib/graphql/query.rb +9 -4
- data/lib/graphql/query/arguments.rb +8 -1
- data/lib/graphql/query/literal_input.rb +2 -1
- data/lib/graphql/query/variables.rb +5 -1
- data/lib/graphql/relay/base_connection.rb +3 -3
- data/lib/graphql/relay/relation_connection.rb +9 -5
- data/lib/graphql/schema.rb +699 -153
- data/lib/graphql/schema/argument.rb +20 -4
- data/lib/graphql/schema/build_from_definition.rb +64 -31
- data/lib/graphql/schema/built_in_types.rb +5 -5
- data/lib/graphql/schema/directive.rb +16 -1
- data/lib/graphql/schema/directive/deprecated.rb +18 -0
- data/lib/graphql/schema/directive/feature.rb +1 -1
- data/lib/graphql/schema/enum.rb +39 -3
- data/lib/graphql/schema/field.rb +39 -9
- data/lib/graphql/schema/field/connection_extension.rb +4 -4
- data/lib/graphql/schema/find_inherited_value.rb +13 -0
- data/lib/graphql/schema/finder.rb +13 -11
- data/lib/graphql/schema/input_object.rb +109 -1
- data/lib/graphql/schema/interface.rb +8 -7
- data/lib/graphql/schema/introspection_system.rb +104 -36
- data/lib/graphql/schema/late_bound_type.rb +1 -0
- data/lib/graphql/schema/list.rb +26 -0
- data/lib/graphql/schema/loader.rb +10 -4
- data/lib/graphql/schema/member.rb +3 -0
- data/lib/graphql/schema/member/base_dsl_methods.rb +23 -13
- data/lib/graphql/schema/member/build_type.rb +1 -1
- data/lib/graphql/schema/member/has_arguments.rb +2 -2
- data/lib/graphql/schema/member/has_fields.rb +15 -6
- data/lib/graphql/schema/member/instrumentation.rb +6 -1
- data/lib/graphql/schema/member/type_system_helpers.rb +1 -1
- data/lib/graphql/schema/member/validates_input.rb +33 -0
- data/lib/graphql/schema/non_null.rb +25 -0
- data/lib/graphql/schema/object.rb +14 -1
- data/lib/graphql/schema/printer.rb +4 -3
- data/lib/graphql/schema/relay_classic_mutation.rb +5 -1
- data/lib/graphql/schema/resolver.rb +20 -2
- data/lib/graphql/schema/scalar.rb +18 -3
- data/lib/graphql/schema/subscription.rb +1 -1
- data/lib/graphql/schema/timeout_middleware.rb +3 -2
- data/lib/graphql/schema/traversal.rb +1 -1
- data/lib/graphql/schema/type_expression.rb +22 -24
- data/lib/graphql/schema/validation.rb +17 -1
- data/lib/graphql/schema/warden.rb +46 -17
- data/lib/graphql/schema/wrapper.rb +1 -1
- data/lib/graphql/static_validation/base_visitor.rb +10 -6
- data/lib/graphql/static_validation/definition_dependencies.rb +21 -12
- data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +4 -4
- data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
- data/lib/graphql/static_validation/type_stack.rb +2 -2
- data/lib/graphql/static_validation/validator.rb +1 -1
- data/lib/graphql/subscriptions.rb +38 -13
- data/lib/graphql/subscriptions/event.rb +24 -7
- data/lib/graphql/subscriptions/instrumentation.rb +1 -1
- data/lib/graphql/subscriptions/subscription_root.rb +0 -1
- data/lib/graphql/tracing/active_support_notifications_tracing.rb +10 -10
- data/lib/graphql/tracing/platform_tracing.rb +1 -2
- data/lib/graphql/tracing/skylight_tracing.rb +1 -0
- data/lib/graphql/unresolved_type_error.rb +2 -2
- data/lib/graphql/upgrader/member.rb +1 -1
- data/lib/graphql/version.rb +1 -1
- metadata +5 -2
@@ -3,11 +3,9 @@ module GraphQL
|
|
3
3
|
class Schema
|
4
4
|
class Scalar < GraphQL::Schema::Member
|
5
5
|
extend GraphQL::Schema::Member::AcceptsDefinition
|
6
|
+
extend GraphQL::Schema::Member::ValidatesInput
|
6
7
|
|
7
8
|
class << self
|
8
|
-
extend Forwardable
|
9
|
-
def_delegators :graphql_definition, :coerce_isolated_input, :coerce_isolated_result
|
10
|
-
|
11
9
|
def coerce_input(val, ctx)
|
12
10
|
val
|
13
11
|
end
|
@@ -38,6 +36,23 @@ module GraphQL
|
|
38
36
|
end
|
39
37
|
@default_scalar
|
40
38
|
end
|
39
|
+
|
40
|
+
def default_scalar?
|
41
|
+
@default_scalar ||= false
|
42
|
+
end
|
43
|
+
|
44
|
+
def validate_non_null_input(value, ctx)
|
45
|
+
result = Query::InputValidationResult.new
|
46
|
+
if coerce_input(value, ctx).nil?
|
47
|
+
str_value = if value == Float::INFINITY
|
48
|
+
""
|
49
|
+
else
|
50
|
+
" #{GraphQL::Language.serialize(value)}"
|
51
|
+
end
|
52
|
+
result.add_problem("Could not coerce value#{str_value} to #{graphql_name}")
|
53
|
+
end
|
54
|
+
result
|
55
|
+
end
|
41
56
|
end
|
42
57
|
end
|
43
58
|
end
|
@@ -29,7 +29,7 @@ module GraphQL
|
|
29
29
|
# propagate null.
|
30
30
|
null false
|
31
31
|
|
32
|
-
def initialize(object:, context:)
|
32
|
+
def initialize(object:, context:, field:)
|
33
33
|
super
|
34
34
|
# Figure out whether this is an update or an initial subscription
|
35
35
|
@mode = context.query.subscription_update? ? :update : :subscribe
|
@@ -35,9 +35,10 @@ module GraphQL
|
|
35
35
|
|
36
36
|
def call(parent_type, parent_object, field_definition, field_args, query_context)
|
37
37
|
ns = query_context.namespace(self.class)
|
38
|
-
|
38
|
+
now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
39
|
+
timeout_at = ns[:timeout_at] ||= now + @max_seconds
|
39
40
|
|
40
|
-
if timeout_at <
|
41
|
+
if timeout_at < now
|
41
42
|
on_timeout(parent_type, parent_object, field_definition, field_args, query_context)
|
42
43
|
else
|
43
44
|
yield
|
@@ -113,7 +113,7 @@ Some late-bound types couldn't be resolved:
|
|
113
113
|
# Find the starting points, then visit them
|
114
114
|
visit_roots = [member.query, member.mutation, member.subscription]
|
115
115
|
if @introspection
|
116
|
-
introspection_types = schema.introspection_system.
|
116
|
+
introspection_types = schema.introspection_system.types.values
|
117
117
|
visit_roots.concat(introspection_types)
|
118
118
|
if member.query
|
119
119
|
member.introspection_system.entry_points.each do |introspection_field|
|
@@ -5,38 +5,36 @@ module GraphQL
|
|
5
5
|
module TypeExpression
|
6
6
|
# Fetch a type from a type map by its AST specification.
|
7
7
|
# Return `nil` if not found.
|
8
|
-
# @param
|
8
|
+
# @param type_owner [#get_type] A thing for looking up types by name
|
9
9
|
# @param ast_node [GraphQL::Language::Nodes::AbstractNode]
|
10
|
-
# @return [GraphQL::
|
11
|
-
def self.build_type(
|
12
|
-
|
13
|
-
|
14
|
-
|
10
|
+
# @return [Class, GraphQL::Schema::NonNull, GraphQL::Schema:List]
|
11
|
+
def self.build_type(type_owner, ast_node)
|
12
|
+
case ast_node
|
13
|
+
when GraphQL::Language::Nodes::TypeName
|
14
|
+
type_owner.get_type(ast_node.name)
|
15
|
+
when GraphQL::Language::Nodes::NonNullType
|
16
|
+
ast_inner_type = ast_node.of_type
|
17
|
+
inner_type = build_type(type_owner, ast_inner_type)
|
18
|
+
wrap_type(inner_type, :to_non_null_type)
|
19
|
+
when GraphQL::Language::Nodes::ListType
|
20
|
+
ast_inner_type = ast_node.of_type
|
21
|
+
inner_type = build_type(type_owner, ast_inner_type)
|
22
|
+
wrap_type(inner_type, :to_list_type)
|
23
|
+
else
|
24
|
+
raise "Invariant: unexpected type from ast: #{ast_node.inspect}"
|
25
|
+
end
|
15
26
|
end
|
16
27
|
|
17
28
|
class << self
|
18
29
|
private
|
19
30
|
|
20
|
-
def
|
21
|
-
|
22
|
-
when GraphQL::Language::Nodes::TypeName
|
23
|
-
types.fetch(ast_node.name, nil)
|
24
|
-
when GraphQL::Language::Nodes::NonNullType
|
25
|
-
ast_inner_type = ast_node.of_type
|
26
|
-
inner_type = build_type(types, ast_inner_type)
|
27
|
-
wrap_type(inner_type, GraphQL::Schema::NonNull)
|
28
|
-
when GraphQL::Language::Nodes::ListType
|
29
|
-
ast_inner_type = ast_node.of_type
|
30
|
-
inner_type = build_type(types, ast_inner_type)
|
31
|
-
wrap_type(inner_type, GraphQL::Schema::List)
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
def wrap_type(of_type, wrapper)
|
36
|
-
if of_type.nil?
|
31
|
+
def wrap_type(type, wrapper_method)
|
32
|
+
if type.nil?
|
37
33
|
nil
|
34
|
+
elsif wrapper_method == :to_list_type || wrapper_method == :to_non_null_type
|
35
|
+
type.public_send(wrapper_method)
|
38
36
|
else
|
39
|
-
wrapper.
|
37
|
+
raise ArgumentError, "Unexpected wrapper method: #{wrapper_method.inspect}"
|
40
38
|
end
|
41
39
|
end
|
42
40
|
end
|
@@ -77,6 +77,18 @@ module GraphQL
|
|
77
77
|
}
|
78
78
|
end
|
79
79
|
|
80
|
+
def self.count_at_least(item_name, minimum_count, get_items_proc)
|
81
|
+
->(type) {
|
82
|
+
items = get_items_proc.call(type)
|
83
|
+
|
84
|
+
if items.size < minimum_count
|
85
|
+
"#{type.name} must define at least #{minimum_count} #{item_name}. #{items.size} defined."
|
86
|
+
else
|
87
|
+
nil
|
88
|
+
end
|
89
|
+
}
|
90
|
+
end
|
91
|
+
|
80
92
|
def self.assert_named_items_are_valid(item_name, get_items_proc)
|
81
93
|
->(type) {
|
82
94
|
items = get_items_proc.call(type)
|
@@ -92,7 +104,9 @@ module GraphQL
|
|
92
104
|
}
|
93
105
|
end
|
94
106
|
|
107
|
+
HAS_AT_LEAST_ONE_FIELD = Rules.count_at_least("field", 1, ->(type) { type.all_fields })
|
95
108
|
FIELDS_ARE_VALID = Rules.assert_named_items_are_valid("field", ->(type) { type.all_fields })
|
109
|
+
HAS_AT_LEAST_ONE_ARGUMENT = Rules.count_at_least("argument", 1, ->(type) { type.arguments })
|
96
110
|
|
97
111
|
HAS_ONE_OR_MORE_POSSIBLE_TYPES = ->(type) {
|
98
112
|
type.possible_types.length >= 1 ? nil : "must have at least one possible type"
|
@@ -140,7 +154,7 @@ module GraphQL
|
|
140
154
|
}
|
141
155
|
|
142
156
|
SCHEMA_CAN_FETCH_IDS = ->(schema) {
|
143
|
-
has_node_field = schema.query && schema.query.
|
157
|
+
has_node_field = schema.query && schema.query.fields.each_value.any?(&:relay_node_field)
|
144
158
|
if has_node_field && schema.object_from_id_proc.nil?
|
145
159
|
"schema contains `node(id:...)` field, so you must define a `object_from_id -> (id, ctx) { ... }` function"
|
146
160
|
else
|
@@ -260,11 +274,13 @@ module GraphQL
|
|
260
274
|
Rules::DESCRIPTION_IS_STRING_OR_NIL,
|
261
275
|
],
|
262
276
|
GraphQL::ObjectType => [
|
277
|
+
Rules::HAS_AT_LEAST_ONE_FIELD,
|
263
278
|
Rules.assert_property_list_of(:interfaces, GraphQL::InterfaceType),
|
264
279
|
Rules::FIELDS_ARE_VALID,
|
265
280
|
Rules::INTERFACES_ARE_IMPLEMENTED,
|
266
281
|
],
|
267
282
|
GraphQL::InputObjectType => [
|
283
|
+
Rules::HAS_AT_LEAST_ONE_ARGUMENT,
|
268
284
|
Rules::ARGUMENTS_ARE_STRING_TO_ARGUMENT,
|
269
285
|
Rules::ARGUMENTS_ARE_VALID,
|
270
286
|
],
|
@@ -39,19 +39,31 @@ module GraphQL
|
|
39
39
|
# @param schema [GraphQL::Schema]
|
40
40
|
# @param deep_check [Boolean]
|
41
41
|
def initialize(filter, context:, schema:)
|
42
|
-
@schema = schema
|
42
|
+
@schema = schema.interpreter? ? schema : schema.graphql_definition
|
43
|
+
# Cache these to avoid repeated hits to the inheritance chain when one isn't present
|
44
|
+
@query = @schema.query
|
45
|
+
@mutation = @schema.mutation
|
46
|
+
@subscription = @schema.subscription
|
43
47
|
@visibility_cache = read_through { |m| filter.call(m, context) }
|
44
48
|
end
|
45
49
|
|
46
50
|
# @return [Array<GraphQL::BaseType>] Visible types in the schema
|
47
51
|
def types
|
48
|
-
@types ||=
|
52
|
+
@types ||= begin
|
53
|
+
vis_types = {}
|
54
|
+
@schema.types.each do |n, t|
|
55
|
+
if visible_type?(t)
|
56
|
+
vis_types[n] = t
|
57
|
+
end
|
58
|
+
end
|
59
|
+
vis_types
|
60
|
+
end
|
49
61
|
end
|
50
62
|
|
51
63
|
# @return [GraphQL::BaseType, nil] The type named `type_name`, if it exists (else `nil`)
|
52
64
|
def get_type(type_name)
|
53
65
|
@visible_types ||= read_through do |name|
|
54
|
-
type_defn = @schema.
|
66
|
+
type_defn = @schema.get_type(name)
|
55
67
|
if type_defn && visible_type?(type_defn)
|
56
68
|
type_defn
|
57
69
|
else
|
@@ -81,7 +93,15 @@ module GraphQL
|
|
81
93
|
|
82
94
|
# @return [Array<GraphQL::BaseType>] The types which may be member of `type_defn`
|
83
95
|
def possible_types(type_defn)
|
84
|
-
@visible_possible_types ||=
|
96
|
+
@visible_possible_types ||= if @schema.is_a?(Class)
|
97
|
+
all_possible_types = @schema.possible_types
|
98
|
+
read_through { |type_defn|
|
99
|
+
pt = all_possible_types[type_defn.graphql_name] || []
|
100
|
+
pt.select { |t| visible_type?(t) }
|
101
|
+
}
|
102
|
+
else
|
103
|
+
read_through { |type_defn| @schema.possible_types(type_defn).select { |t| visible_type?(t) } }
|
104
|
+
end
|
85
105
|
@visible_possible_types[type_defn]
|
86
106
|
end
|
87
107
|
|
@@ -136,28 +156,37 @@ module GraphQL
|
|
136
156
|
end
|
137
157
|
|
138
158
|
def visible_type?(type_defn)
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
159
|
+
@type_visibility ||= read_through do |type_defn|
|
160
|
+
next false unless visible?(type_defn)
|
161
|
+
next true if root_type?(type_defn) || type_defn.introspection?
|
162
|
+
|
163
|
+
if type_defn.kind.union?
|
164
|
+
visible_possible_types?(type_defn) && (referenced?(type_defn) || orphan_type?(type_defn))
|
165
|
+
elsif type_defn.kind.interface?
|
166
|
+
visible_possible_types?(type_defn)
|
167
|
+
else
|
168
|
+
referenced?(type_defn) || visible_abstract_type?(type_defn)
|
169
|
+
end
|
149
170
|
end
|
171
|
+
|
172
|
+
@type_visibility[type_defn]
|
150
173
|
end
|
151
174
|
|
152
175
|
def root_type?(type_defn)
|
153
|
-
@
|
176
|
+
@query == type_defn ||
|
177
|
+
@mutation == type_defn ||
|
178
|
+
@subscription == type_defn
|
154
179
|
end
|
155
180
|
|
156
181
|
def referenced?(type_defn)
|
157
|
-
|
182
|
+
@references_to ||= @schema.references_to
|
183
|
+
graphql_name = type_defn.unwrap.graphql_name
|
184
|
+
members = @references_to[graphql_name] || NO_REFERENCES
|
158
185
|
members.any? { |m| visible?(m) }
|
159
186
|
end
|
160
187
|
|
188
|
+
NO_REFERENCES = [].freeze
|
189
|
+
|
161
190
|
def orphan_type?(type_defn)
|
162
191
|
@schema.orphan_types.include?(type_defn)
|
163
192
|
end
|
@@ -170,7 +199,7 @@ module GraphQL
|
|
170
199
|
end
|
171
200
|
|
172
201
|
def visible_possible_types?(type_defn)
|
173
|
-
|
202
|
+
possible_types(type_defn).any? { |t| visible_type?(t) }
|
174
203
|
end
|
175
204
|
|
176
205
|
def visible?(member)
|
@@ -71,7 +71,7 @@ module GraphQL
|
|
71
71
|
module ContextMethods
|
72
72
|
def on_operation_definition(node, parent)
|
73
73
|
object_type = @schema.root_type_for_operation(node.operation_type)
|
74
|
-
|
74
|
+
push_type(object_type)
|
75
75
|
@path.push("#{node.operation_type}#{node.name ? " #{node.name}" : ""}")
|
76
76
|
super
|
77
77
|
@object_types.pop
|
@@ -98,9 +98,9 @@ module GraphQL
|
|
98
98
|
@field_definitions.push(field_definition)
|
99
99
|
if !field_definition.nil?
|
100
100
|
next_object_type = field_definition.type.unwrap
|
101
|
-
|
101
|
+
push_type(next_object_type)
|
102
102
|
else
|
103
|
-
|
103
|
+
push_type(nil)
|
104
104
|
end
|
105
105
|
@path.push(node.alias || node.name)
|
106
106
|
super
|
@@ -120,7 +120,7 @@ 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.
|
123
|
+
arg_type.arguments[node.name]
|
124
124
|
else
|
125
125
|
nil
|
126
126
|
end
|
@@ -187,15 +187,19 @@ module GraphQL
|
|
187
187
|
|
188
188
|
def on_fragment_with_type(node)
|
189
189
|
object_type = if node.type
|
190
|
-
@schema.
|
190
|
+
@schema.get_type(node.type.name)
|
191
191
|
else
|
192
192
|
@object_types.last
|
193
193
|
end
|
194
|
-
|
194
|
+
push_type(object_type)
|
195
195
|
yield(node)
|
196
196
|
@object_types.pop
|
197
197
|
@path.pop
|
198
198
|
end
|
199
|
+
|
200
|
+
def push_type(t)
|
201
|
+
@object_types.push(t)
|
202
|
+
end
|
199
203
|
end
|
200
204
|
|
201
205
|
private
|
@@ -11,11 +11,11 @@ module GraphQL
|
|
11
11
|
super
|
12
12
|
@defdep_node_paths = {}
|
13
13
|
|
14
|
-
# { name => node } pairs for fragments
|
15
|
-
@defdep_fragment_definitions = {}
|
14
|
+
# { name => [node, ...] } pairs for fragments (although duplicate-named fragments are _invalid_, they are _possible_)
|
15
|
+
@defdep_fragment_definitions = Hash.new{ |h, k| h[k] = [] }
|
16
16
|
|
17
17
|
# This tracks dependencies from fragment to Node where it was used
|
18
|
-
# {
|
18
|
+
# { fragment_definition_name => [dependent_node, dependent_node]}
|
19
19
|
@defdep_dependent_definitions = Hash.new { |h, k| h[k] = Set.new }
|
20
20
|
|
21
21
|
# First-level usages of spreads within definitions
|
@@ -32,7 +32,7 @@ module GraphQL
|
|
32
32
|
def on_document(node, parent)
|
33
33
|
node.definitions.each do |definition|
|
34
34
|
if definition.is_a? GraphQL::Language::Nodes::FragmentDefinition
|
35
|
-
@defdep_fragment_definitions[definition.name]
|
35
|
+
@defdep_fragment_definitions[definition.name] << definition
|
36
36
|
end
|
37
37
|
end
|
38
38
|
super
|
@@ -42,7 +42,7 @@ module GraphQL
|
|
42
42
|
end
|
43
43
|
|
44
44
|
def on_operation_definition(node, prev_node)
|
45
|
-
@defdep_node_paths[node] = NodeWithPath.new(node, context.path)
|
45
|
+
@defdep_node_paths[node.name] = NodeWithPath.new(node, context.path)
|
46
46
|
@defdep_current_parent = node
|
47
47
|
super
|
48
48
|
@defdep_current_parent = nil
|
@@ -59,7 +59,7 @@ module GraphQL
|
|
59
59
|
@defdep_node_paths[node] = NodeWithPath.new(node, context.path)
|
60
60
|
|
61
61
|
# Track both sides of the dependency
|
62
|
-
@defdep_dependent_definitions[
|
62
|
+
@defdep_dependent_definitions[node.name] << @defdep_current_parent
|
63
63
|
@defdep_immediate_dependencies[@defdep_current_parent] << node
|
64
64
|
super
|
65
65
|
end
|
@@ -116,24 +116,28 @@ module GraphQL
|
|
116
116
|
dependency_map = DependencyMap.new
|
117
117
|
# Don't allow the loop to run more times
|
118
118
|
# than the number of fragments in the document
|
119
|
-
max_loops =
|
119
|
+
max_loops = 0
|
120
|
+
@defdep_fragment_definitions.each_value do |v|
|
121
|
+
max_loops += v.size
|
122
|
+
end
|
123
|
+
|
120
124
|
loops = 0
|
121
125
|
|
122
126
|
# Instead of tracking independent fragments _as you visit_,
|
123
127
|
# determine them at the end. This way, we can treat fragments with the
|
124
128
|
# same name as if they were the same name. If _any_ of the fragments
|
125
129
|
# with that name has a dependency, we record it.
|
126
|
-
independent_fragment_nodes = @defdep_fragment_definitions.values - @defdep_immediate_dependencies.keys
|
130
|
+
independent_fragment_nodes = @defdep_fragment_definitions.values.flatten - @defdep_immediate_dependencies.keys
|
127
131
|
|
128
132
|
while fragment_node = independent_fragment_nodes.pop
|
129
133
|
loops += 1
|
130
134
|
if loops > max_loops
|
131
|
-
raise("Resolution loops exceeded the number of definitions; infinite loop detected.")
|
135
|
+
raise("Resolution loops exceeded the number of definitions; infinite loop detected. (Max: #{max_loops}, Current: #{loops})")
|
132
136
|
end
|
133
137
|
# Since it's independent, let's remove it from here.
|
134
138
|
# That way, we can use the remainder to identify cycles
|
135
139
|
@defdep_immediate_dependencies.delete(fragment_node)
|
136
|
-
fragment_usages = @defdep_dependent_definitions[fragment_node]
|
140
|
+
fragment_usages = @defdep_dependent_definitions[fragment_node.name]
|
137
141
|
if fragment_usages.empty?
|
138
142
|
# If we didn't record any usages during the visit,
|
139
143
|
# then this fragment is unused.
|
@@ -151,10 +155,15 @@ module GraphQL
|
|
151
155
|
if block_given?
|
152
156
|
yield(definition_node, removed, fragment_node)
|
153
157
|
end
|
154
|
-
if remaining.empty? &&
|
158
|
+
if remaining.empty? &&
|
159
|
+
definition_node.is_a?(GraphQL::Language::Nodes::FragmentDefinition) &&
|
160
|
+
definition_node.name != fragment_node.name
|
155
161
|
# If all of this definition's dependencies have
|
156
162
|
# been resolved, we can now resolve its
|
157
163
|
# own dependents.
|
164
|
+
#
|
165
|
+
# But, it's possible to have a duplicate-named fragment here.
|
166
|
+
# Skip it in that case
|
158
167
|
independent_fragment_nodes << definition_node
|
159
168
|
end
|
160
169
|
end
|
@@ -166,7 +175,7 @@ module GraphQL
|
|
166
175
|
# then they're still in there
|
167
176
|
@defdep_immediate_dependencies.each do |defn_node, deps|
|
168
177
|
deps.each do |spread|
|
169
|
-
if
|
178
|
+
if !@defdep_fragment_definitions.key?(spread.name)
|
170
179
|
dependency_map.unmet_dependencies[@defdep_node_paths[defn_node]] << @defdep_node_paths[spread]
|
171
180
|
deps.delete(spread)
|
172
181
|
end
|