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.
Files changed (110) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/core.rb +1 -0
  3. data/lib/generators/graphql/install_generator.rb +1 -0
  4. data/lib/generators/graphql/mutation_generator.rb +1 -1
  5. data/lib/generators/graphql/templates/base_field.erb +0 -4
  6. data/lib/generators/graphql/templates/base_mutation.erb +8 -0
  7. data/lib/generators/graphql/templates/graphql_controller.erb +5 -0
  8. data/lib/generators/graphql/templates/mutation.erb +1 -1
  9. data/lib/generators/graphql/templates/schema.erb +1 -1
  10. data/lib/graphql.rb +4 -1
  11. data/lib/graphql/analysis/ast.rb +14 -13
  12. data/lib/graphql/analysis/ast/analyzer.rb +23 -4
  13. data/lib/graphql/analysis/ast/field_usage.rb +1 -1
  14. data/lib/graphql/analysis/ast/max_query_complexity.rb +3 -3
  15. data/lib/graphql/analysis/ast/max_query_depth.rb +7 -3
  16. data/lib/graphql/analysis/ast/query_complexity.rb +2 -2
  17. data/lib/graphql/analysis/ast/visitor.rb +3 -3
  18. data/lib/graphql/base_type.rb +1 -1
  19. data/lib/graphql/directive.rb +0 -1
  20. data/lib/graphql/directive/deprecated_directive.rb +1 -12
  21. data/lib/graphql/execution/errors.rb +4 -8
  22. data/lib/graphql/execution/interpreter.rb +5 -11
  23. data/lib/graphql/execution/interpreter/runtime.rb +56 -48
  24. data/lib/graphql/execution/lazy/lazy_method_map.rb +4 -0
  25. data/lib/graphql/execution/lookahead.rb +5 -5
  26. data/lib/graphql/execution/multiplex.rb +10 -0
  27. data/lib/graphql/function.rb +1 -1
  28. data/lib/graphql/input_object_type.rb +3 -2
  29. data/lib/graphql/interface_type.rb +1 -1
  30. data/lib/graphql/introspection/base_object.rb +2 -5
  31. data/lib/graphql/introspection/directive_type.rb +1 -1
  32. data/lib/graphql/introspection/entry_points.rb +6 -6
  33. data/lib/graphql/introspection/schema_type.rb +1 -6
  34. data/lib/graphql/introspection/type_type.rb +5 -5
  35. data/lib/graphql/language.rb +1 -1
  36. data/lib/graphql/language/block_string.rb +2 -2
  37. data/lib/graphql/language/definition_slice.rb +21 -10
  38. data/lib/graphql/language/document_from_schema_definition.rb +42 -42
  39. data/lib/graphql/language/lexer.rb +49 -48
  40. data/lib/graphql/language/lexer.rl +49 -48
  41. data/lib/graphql/language/nodes.rb +11 -8
  42. data/lib/graphql/language/parser.rb +4 -1
  43. data/lib/graphql/language/parser.y +4 -1
  44. data/lib/graphql/language/token.rb +1 -1
  45. data/lib/graphql/pagination/array_connection.rb +0 -1
  46. data/lib/graphql/pagination/connection.rb +31 -10
  47. data/lib/graphql/pagination/connections.rb +7 -2
  48. data/lib/graphql/pagination/relation_connection.rb +1 -7
  49. data/lib/graphql/query.rb +9 -4
  50. data/lib/graphql/query/arguments.rb +8 -1
  51. data/lib/graphql/query/literal_input.rb +2 -1
  52. data/lib/graphql/query/variables.rb +5 -1
  53. data/lib/graphql/relay/base_connection.rb +3 -3
  54. data/lib/graphql/relay/relation_connection.rb +9 -5
  55. data/lib/graphql/schema.rb +699 -153
  56. data/lib/graphql/schema/argument.rb +20 -4
  57. data/lib/graphql/schema/build_from_definition.rb +64 -31
  58. data/lib/graphql/schema/built_in_types.rb +5 -5
  59. data/lib/graphql/schema/directive.rb +16 -1
  60. data/lib/graphql/schema/directive/deprecated.rb +18 -0
  61. data/lib/graphql/schema/directive/feature.rb +1 -1
  62. data/lib/graphql/schema/enum.rb +39 -3
  63. data/lib/graphql/schema/field.rb +39 -9
  64. data/lib/graphql/schema/field/connection_extension.rb +4 -4
  65. data/lib/graphql/schema/find_inherited_value.rb +13 -0
  66. data/lib/graphql/schema/finder.rb +13 -11
  67. data/lib/graphql/schema/input_object.rb +109 -1
  68. data/lib/graphql/schema/interface.rb +8 -7
  69. data/lib/graphql/schema/introspection_system.rb +104 -36
  70. data/lib/graphql/schema/late_bound_type.rb +1 -0
  71. data/lib/graphql/schema/list.rb +26 -0
  72. data/lib/graphql/schema/loader.rb +10 -4
  73. data/lib/graphql/schema/member.rb +3 -0
  74. data/lib/graphql/schema/member/base_dsl_methods.rb +23 -13
  75. data/lib/graphql/schema/member/build_type.rb +1 -1
  76. data/lib/graphql/schema/member/has_arguments.rb +2 -2
  77. data/lib/graphql/schema/member/has_fields.rb +15 -6
  78. data/lib/graphql/schema/member/instrumentation.rb +6 -1
  79. data/lib/graphql/schema/member/type_system_helpers.rb +1 -1
  80. data/lib/graphql/schema/member/validates_input.rb +33 -0
  81. data/lib/graphql/schema/non_null.rb +25 -0
  82. data/lib/graphql/schema/object.rb +14 -1
  83. data/lib/graphql/schema/printer.rb +4 -3
  84. data/lib/graphql/schema/relay_classic_mutation.rb +5 -1
  85. data/lib/graphql/schema/resolver.rb +20 -2
  86. data/lib/graphql/schema/scalar.rb +18 -3
  87. data/lib/graphql/schema/subscription.rb +1 -1
  88. data/lib/graphql/schema/timeout_middleware.rb +3 -2
  89. data/lib/graphql/schema/traversal.rb +1 -1
  90. data/lib/graphql/schema/type_expression.rb +22 -24
  91. data/lib/graphql/schema/validation.rb +17 -1
  92. data/lib/graphql/schema/warden.rb +46 -17
  93. data/lib/graphql/schema/wrapper.rb +1 -1
  94. data/lib/graphql/static_validation/base_visitor.rb +10 -6
  95. data/lib/graphql/static_validation/definition_dependencies.rb +21 -12
  96. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +4 -4
  97. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
  98. data/lib/graphql/static_validation/type_stack.rb +2 -2
  99. data/lib/graphql/static_validation/validator.rb +1 -1
  100. data/lib/graphql/subscriptions.rb +38 -13
  101. data/lib/graphql/subscriptions/event.rb +24 -7
  102. data/lib/graphql/subscriptions/instrumentation.rb +1 -1
  103. data/lib/graphql/subscriptions/subscription_root.rb +0 -1
  104. data/lib/graphql/tracing/active_support_notifications_tracing.rb +10 -10
  105. data/lib/graphql/tracing/platform_tracing.rb +1 -2
  106. data/lib/graphql/tracing/skylight_tracing.rb +1 -0
  107. data/lib/graphql/unresolved_type_error.rb +2 -2
  108. data/lib/graphql/upgrader/member.rb +1 -1
  109. data/lib/graphql/version.rb +1 -1
  110. 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
- timeout_at = ns[:timeout_at] ||= Time.now + @max_seconds
38
+ now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
39
+ timeout_at = ns[:timeout_at] ||= now + @max_seconds
39
40
 
40
- if timeout_at < Time.now
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.object_types
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 types [GraphQL::Schema::TypeMap]
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::BaseType, nil]
11
- def self.build_type(types, ast_node)
12
- t = type_from_ast(types, ast_node)
13
- # maybe nil:
14
- t ? t.graphql_definition : t
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 type_from_ast(types, ast_node)
21
- case ast_node
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.new(of_type)
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.all_fields.any?(&:relay_node_field)
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 ||= @schema.types.each_value.select { |t| visible_type?(t) }
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.types.fetch(name, nil)
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 ||= read_through { |type_defn| @schema.possible_types(type_defn).select { |t| visible_type?(t) } }
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
- return false unless visible?(type_defn)
140
- return true if root_type?(type_defn)
141
- return true if type_defn.introspection?
142
-
143
- if type_defn.kind.union?
144
- visible_possible_types?(type_defn) && (referenced?(type_defn) || orphan_type?(type_defn))
145
- elsif type_defn.kind.interface?
146
- visible_possible_types?(type_defn)
147
- else
148
- referenced?(type_defn) || visible_abstract_type?(type_defn)
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
- @schema.root_types.include?(type_defn)
176
+ @query == type_defn ||
177
+ @mutation == type_defn ||
178
+ @subscription == type_defn
154
179
  end
155
180
 
156
181
  def referenced?(type_defn)
157
- members = @schema.references_to(type_defn.unwrap.name)
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
- @schema.possible_types(type_defn).any? { |t| visible_type?(t) }
202
+ possible_types(type_defn).any? { |t| visible_type?(t) }
174
203
  end
175
204
 
176
205
  def visible?(member)
@@ -14,7 +14,7 @@ module GraphQL
14
14
  end
15
15
 
16
16
  def to_graphql
17
- raise NotImplementedError
17
+ raise GraphQL::RequiredImplementationMissingError
18
18
  end
19
19
 
20
20
  def unwrap
@@ -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
- @object_types.push(object_type)
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
- @object_types.push(next_object_type)
101
+ push_type(next_object_type)
102
102
  else
103
- @object_types.push(nil)
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.input_fields[node.name]
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.types.fetch(node.type.name, nil)
190
+ @schema.get_type(node.type.name)
191
191
  else
192
192
  @object_types.last
193
193
  end
194
- @object_types.push(object_type)
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
- # { fragment_definition_node => [dependent_node, dependent_node]}
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] = definition
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[@defdep_fragment_definitions[node.name]] << @defdep_current_parent
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 = @defdep_fragment_definitions.size
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? && definition_node.is_a?(GraphQL::Language::Nodes::FragmentDefinition)
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 @defdep_fragment_definitions[spread.name].nil?
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