graphql 1.10.0.pre1 → 1.10.0.pre2

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.
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