graphql 1.10.0 → 1.10.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5410e491e639ee266d3c13a8f3073be2d8270289548f6c63e10a056fe5ff8a43
4
- data.tar.gz: 9b6a6a7dd62e3946b49b0e46c8dbf7745feeb4b48cb931b9d9d2deb9221bb43a
3
+ metadata.gz: 206501adb93cafefa37313d5cb3bc58413701cd5a2cc4028a9b749c119d7868d
4
+ data.tar.gz: 34e9b0f601cb1517c1060b4c964e6a25e020540b115d876af8c3c775cfd28793
5
5
  SHA512:
6
- metadata.gz: 78f06f66e32517103a861c2adeeeac0f090dbb111d4827a22f3f7a398b8825b10fc1d89bfcc96be3543e8c043351a7023ef1542284bc5ce60518ed5eeb6e6c70
7
- data.tar.gz: 53264646f1a059b97494503b350de5908d2ca70392c749426d6863275ac4b99f4629dbf18ae21fc21f1bd85fbb02ad4f7f17b7a8ce54a64e0aa89ee6bbee253b
6
+ metadata.gz: e68296c1cb93a1164ce0123a77fc0ec7efd86e51afd029b808bb3f466b2287744d1e54b01f685b1bcf71c7e4265a064b1e88ee5cfbcfc6647312758d2e7962f9
7
+ data.tar.gz: 01de56922522f3984950ddfc1821c7d15fd27ea7055470cf6102f9af9fa3c8402eefa41061c82d83a9461d4ae9df3f45935e10775ac6322ef9e0c40bd978c3a1
@@ -7,6 +7,14 @@ require "forwardable"
7
7
  require_relative "./graphql/railtie" if defined? Rails::Railtie
8
8
 
9
9
  module GraphQL
10
+ # forwards-compat for argument handling
11
+ module Ruby2Keywords
12
+ if RUBY_VERSION < "2.7"
13
+ def ruby2_keywords(*)
14
+ end
15
+ end
16
+ end
17
+
10
18
  class Error < StandardError
11
19
  end
12
20
 
@@ -8,7 +8,7 @@ module GraphQL
8
8
  # - `complexities_on_type` holds complexity scores for each type in an IRep node
9
9
  def initialize(query)
10
10
  super
11
- @complexities_on_type = [ConcreteTypeComplexity.new]
11
+ @complexities_on_type_by_query = {}
12
12
  end
13
13
 
14
14
  # Overide this method to use the complexity result
@@ -16,17 +16,68 @@ module GraphQL
16
16
  max_possible_complexity
17
17
  end
18
18
 
19
+ class ScopedTypeComplexity
20
+ # A single proc for {#scoped_children} hashes. Use this to avoid repeated allocations,
21
+ # since the lexical binding isn't important.
22
+ HASH_CHILDREN = ->(h, k) { h[k] = {} }
23
+
24
+ # @param node [Language::Nodes::Field] The AST node; used for providing argument values when necessary
25
+ # @param field_definition [GraphQL::Field, GraphQL::Schema::Field] Used for getting the `.complexity` configuration
26
+ # @param query [GrpahQL::Query] Used for `query.possible_types`
27
+ def initialize(node, field_definition, query)
28
+ @field_definition = field_definition
29
+ @query = query
30
+ @node = node
31
+ @scoped_children = nil
32
+ end
33
+
34
+ # Returns true if this field has no selections, ie, it's a scalar.
35
+ # We need a quick way to check whether we should continue traversing.
36
+ def terminal?
37
+ @scoped_children.nil?
38
+ end
39
+
40
+ # This value is only calculated when asked for to avoid needless hash allocations.
41
+ # Also, if it's never asked for, we determine that this scope complexity
42
+ # is a scalar field ({#terminal?}).
43
+ # @return [Hash<Hash<Class => ScopedTypeComplexity>]
44
+ def scoped_children
45
+ @scoped_children ||= Hash.new(&HASH_CHILDREN)
46
+ end
47
+
48
+ def own_complexity(child_complexity)
49
+ defined_complexity = @field_definition.complexity
50
+ case defined_complexity
51
+ when Proc
52
+ arguments = @query.arguments_for(@node, @field_definition)
53
+ defined_complexity.call(@query.context, arguments, child_complexity)
54
+ when Numeric
55
+ defined_complexity + child_complexity
56
+ else
57
+ raise("Invalid complexity: #{defined_complexity.inspect} on #{@field_definition.name}")
58
+ end
59
+ end
60
+ end
61
+
19
62
  def on_enter_field(node, parent, visitor)
20
63
  # We don't want to visit fragment definitions,
21
64
  # we'll visit them when we hit the spreads instead
22
65
  return if visitor.visiting_fragment_definition?
23
66
  return if visitor.skipping?
24
-
25
- if visitor.type_definition.kind.abstract?
26
- @complexities_on_type.push(AbstractTypeComplexity.new)
27
- else
28
- @complexities_on_type.push(ConcreteTypeComplexity.new)
29
- end
67
+ parent_type = visitor.parent_type_definition
68
+ field_key = node.alias || node.name
69
+ # Find the complexity calculation for this field --
70
+ # if we're re-entering a selection, we'll already have one.
71
+ # Otherwise, make a new one and store it.
72
+ #
73
+ # `node` and `visitor.field_definition` may appear from a cache,
74
+ # but I think that's ok. If the arguments _didn't_ match,
75
+ # then the query would have been rejected as invalid.
76
+ complexities_on_type = @complexities_on_type_by_query[visitor.query] ||= [ScopedTypeComplexity.new(nil, nil, query)]
77
+
78
+ complexity = complexities_on_type.last.scoped_children[parent_type][field_key] ||= ScopedTypeComplexity.new(node, visitor.field_definition, visitor.query)
79
+ # Push it on the stack.
80
+ complexities_on_type.push(complexity)
30
81
  end
31
82
 
32
83
  def on_leave_field(node, parent, visitor)
@@ -34,87 +85,126 @@ module GraphQL
34
85
  # we'll visit them when we hit the spreads instead
35
86
  return if visitor.visiting_fragment_definition?
36
87
  return if visitor.skipping?
37
-
38
- type_complexities = @complexities_on_type.pop
39
- child_complexity = type_complexities.max_possible_complexity
40
- own_complexity = get_complexity(node, visitor.field_definition, child_complexity, visitor)
41
-
42
- if @complexities_on_type.last.is_a?(AbstractTypeComplexity)
43
- key = selection_key(visitor.response_path, visitor.query)
44
- parent_type = visitor.parent_type_definition
45
- visitor.query.possible_types(parent_type).each do |type|
46
- @complexities_on_type.last.merge(type, key, own_complexity)
47
- end
48
- else
49
- @complexities_on_type.last.merge(own_complexity)
50
- end
88
+ complexities_on_type = @complexities_on_type_by_query[visitor.query]
89
+ complexities_on_type.pop
51
90
  end
52
91
 
53
92
  # @return [Integer]
54
93
  def max_possible_complexity
55
- @complexities_on_type.last.max_possible_complexity
56
- end
57
-
58
- private
59
-
60
- def selection_key(response_path, query)
61
- # We add the query object id to support multiplex queries
62
- # even if they have the same response path, they should
63
- # always be added.
64
- "#{response_path.join(".")}-#{query.object_id}"
65
- end
66
-
67
- # Get a complexity value for a field,
68
- # by getting the number or calling its proc
69
- def get_complexity(ast_node, field_defn, child_complexity, visitor)
70
- # Return if we've visited this response path before (not counting duplicates)
71
- defined_complexity = field_defn.complexity
72
-
73
- arguments = visitor.arguments_for(ast_node, field_defn)
74
-
75
- case defined_complexity
76
- when Proc
77
- defined_complexity.call(visitor.query.context, arguments, child_complexity)
78
- when Numeric
79
- defined_complexity + (child_complexity || 0)
80
- else
81
- raise("Invalid complexity: #{defined_complexity.inspect} on #{field_defn.name}")
94
+ @complexities_on_type_by_query.reduce(0) do |total, (query, complexities_on_type)|
95
+ root_complexity = complexities_on_type.last
96
+ # Use this entry point to calculate the total complexity
97
+ total_complexity_for_query = ComplexityMergeFunctions.merged_max_complexity_for_scopes(query, [root_complexity.scoped_children])
98
+ total + total_complexity_for_query
82
99
  end
83
100
  end
84
101
 
85
- # Selections on an object may apply differently depending on what is _actually_ returned by the resolve function.
86
- # Find the maximum possible complexity among those combinations.
87
- class AbstractTypeComplexity
88
- def initialize
89
- @types = Hash.new { |h, k| h[k] = {} }
102
+ # These functions use `ScopedTypeComplexity` objects,
103
+ # especially their `scoped_children`, to traverse down the tree
104
+ # and find the max complexity for any possible runtime type.
105
+ # Yowza.
106
+ module ComplexityMergeFunctions
107
+ module_function
108
+ # When looking at two selection scopes, figure out whether the selections on
109
+ # `right_scope` should be applied when analyzing `left_scope`.
110
+ # This is like the `Typecast.subtype?`, except it's using query-specific type filtering.
111
+ def applies_to?(query, left_scope, right_scope)
112
+ if left_scope == right_scope
113
+ # This can happen when several branches are being analyzed together
114
+ true
115
+ else
116
+ # Check if these two scopes have _any_ types in common.
117
+ possible_right_types = query.possible_types(right_scope)
118
+ possible_left_types = query.possible_types(left_scope)
119
+ !(possible_right_types & possible_left_types).empty?
120
+ end
90
121
  end
91
122
 
92
- # Return the max possible complexity for types in this selection
93
- def max_possible_complexity
94
- max = 0
95
- @types.each_value do |fields|
96
- complexity = fields.each_value.inject(:+)
97
- max = complexity if complexity > max
123
+ def merged_max_complexity_for_scopes(query, scoped_children_hashes)
124
+ # Figure out what scopes are possible here.
125
+ # Use a hash, but ignore the values; it's just a fast way to work with the keys.
126
+ all_scopes = {}
127
+ scoped_children_hashes.each do |h|
128
+ all_scopes.merge!(h)
98
129
  end
99
- max
100
- end
101
130
 
102
- # Store the complexity for the branch on `type_defn`.
103
- # Later we will see if this is the max complexity among branches.
104
- def merge(type_defn, key, complexity)
105
- @types[type_defn][key] = complexity
106
- end
107
- end
131
+ # If an abstract scope is present, but _all_ of its concrete types
132
+ # are also in the list, remove it from the list of scopes to check,
133
+ # because every possible type is covered by a concrete type.
134
+ # (That is, there are no remainder types to check.)
135
+ all_scopes.reject! do |scope, _|
136
+ scope.kind.abstract? && (
137
+ query.possible_types(scope).all? { |t| all_scopes.include?(t) }
138
+ )
139
+ end
108
140
 
109
- class ConcreteTypeComplexity
110
- attr_reader :max_possible_complexity
141
+ # This will hold `{ type => int }` pairs, one for each possible branch
142
+ complexity_by_scope = {}
143
+
144
+ # For each scope,
145
+ # find the lexical selections that might apply to it,
146
+ # and gather them together into an array.
147
+ # Then, treat the set of selection hashes
148
+ # as a set and calculate the complexity for them as a unit
149
+ all_scopes.each do |scope, _|
150
+ # These will be the selections on `scope`
151
+ children_for_scope = []
152
+ scoped_children_hashes.each do |sc_h|
153
+ sc_h.each do |inner_scope, children_hash|
154
+ if applies_to?(query, scope, inner_scope)
155
+ children_for_scope << children_hash
156
+ end
157
+ end
158
+ end
159
+
160
+ # Calculate the complexity for `scope`, merging all
161
+ # possible lexical branches.
162
+ complexity_value = merged_max_complexity(query, children_for_scope)
163
+ complexity_by_scope[scope] = complexity_value
164
+ end
111
165
 
112
- def initialize
113
- @max_possible_complexity = 0
166
+ # Return the max complexity among all scopes
167
+ complexity_by_scope.each_value.max
114
168
  end
115
169
 
116
- def merge(complexity)
117
- @max_possible_complexity += complexity
170
+ # @param children_for_scope [Array<Hash>] An array of `scoped_children[scope]` hashes (`{field_key => complexity}`)
171
+ # @return [Integer] Complexity value for all these selections in the current scope
172
+ def merged_max_complexity(query, children_for_scope)
173
+ all_keys = []
174
+ children_for_scope.each do |c|
175
+ all_keys.concat(c.keys)
176
+ end
177
+ all_keys.uniq!
178
+ complexity_for_keys = {}
179
+ all_keys.each do |child_key|
180
+
181
+ scoped_children_for_key = nil
182
+ complexity_for_key = nil
183
+ children_for_scope.each do |children_hash|
184
+ if children_hash.key?(child_key)
185
+ complexity_for_key = children_hash[child_key]
186
+ if complexity_for_key.terminal?
187
+ # Assume that all terminals would return the same complexity
188
+ # Since it's a terminal, its child complexity is zero.
189
+ complexity_for_key = complexity_for_key.own_complexity(0)
190
+ complexity_for_keys[child_key] = complexity_for_key
191
+ else
192
+ scoped_children_for_key ||= []
193
+ scoped_children_for_key << complexity_for_key.scoped_children
194
+ end
195
+ end
196
+ end
197
+
198
+ if scoped_children_for_key
199
+ child_complexity = merged_max_complexity_for_scopes(query, scoped_children_for_key)
200
+ # This is the _last_ one we visited; assume it's representative.
201
+ max_complexity = complexity_for_key.own_complexity(child_complexity)
202
+ complexity_for_keys[child_key] = max_complexity
203
+ end
204
+ end
205
+
206
+ # Calculate the child complexity by summing the complexity of all selections
207
+ complexity_for_keys.each_value.inject(0, &:+)
118
208
  end
119
209
  end
120
210
  end
@@ -1,10 +1,12 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module GraphQL
3
4
  module Define
4
5
  # This object delegates most methods to a dictionary of functions, {@dictionary}.
5
6
  # {@target} is passed to the specified function, along with any arguments and block.
6
7
  # This allows a method-based DSL without adding methods to the defined class.
7
8
  class DefinedObjectProxy
9
+ extend GraphQL::Ruby2Keywords
8
10
  # The object which will be defined by definition functions
9
11
  attr_reader :target
10
12
 
@@ -32,16 +34,11 @@ module GraphQL
32
34
  end
33
35
 
34
36
  # Lookup a function from the dictionary and call it if it's found.
35
- def method_missing(name, *args, **kwargs, &block)
37
+ ruby2_keywords
38
+ def method_missing(name, *args, &block)
36
39
  definition = @dictionary[name]
37
40
  if definition
38
- # Avoid passing `kwargs` when it's not used.
39
- # Ruby 2.7 does fine here, but older Rubies receive too many arguments.
40
- if kwargs.any?
41
- definition.call(@target, *args, **kwargs, &block)
42
- else
43
- definition.call(@target, *args, &block)
44
- end
41
+ definition.call(@target, *args, &block)
45
42
  else
46
43
  msg = "#{@target.class.name} can't define '#{name}'"
47
44
  raise NoDefinitionError, msg, caller
@@ -191,12 +191,18 @@ module GraphQL
191
191
  end
192
192
 
193
193
  class AssignAttribute
194
+ extend GraphQL::Ruby2Keywords
195
+
194
196
  def initialize(attr_name)
195
197
  @attr_assign_method = :"#{attr_name}="
196
198
  end
197
199
 
198
- def call(defn, value)
199
- defn.public_send(@attr_assign_method, value)
200
+ # Even though we're just using the first value here,
201
+ # We have to add a splat here to use `ruby2_keywords`,
202
+ # so that it will accept a `[{}]` input from the caller.
203
+ ruby2_keywords
204
+ def call(defn, *value)
205
+ defn.public_send(@attr_assign_method, value.first)
200
206
  end
201
207
  end
202
208
  end
@@ -51,7 +51,7 @@ module GraphQL
51
51
 
52
52
  # @return [Hash<Symbol, Object>]
53
53
  def arguments
54
- @arguments ||= @field && ArgumentHelpers.arguments(@query, nil, @field, ast_nodes.first)
54
+ @arguments ||= @field && ArgumentHelpers.arguments(@query, @field, ast_nodes.first)
55
55
  end
56
56
 
57
57
  # True if this node has a selection on `field_name`.
@@ -203,8 +203,22 @@ module GraphQL
203
203
  end
204
204
  end
205
205
 
206
+ def skipped_by_directive?(ast_selection)
207
+ ast_selection.directives.each do |directive|
208
+ dir_defn = @query.schema.directives.fetch(directive.name)
209
+ directive_class = dir_defn.type_class
210
+ if directive_class
211
+ dir_args = GraphQL::Execution::Lookahead::ArgumentHelpers.arguments(@query, dir_defn, directive)
212
+ return true unless directive_class.static_include?(dir_args, @query.context)
213
+ end
214
+ end
215
+ false
216
+ end
217
+
206
218
  def find_selections(subselections_by_type, selections_on_type, selected_type, ast_selections, arguments)
207
219
  ast_selections.each do |ast_selection|
220
+ next if skipped_by_directive?(ast_selection)
221
+
208
222
  case ast_selection
209
223
  when GraphQL::Language::Nodes::Field
210
224
  response_key = ast_selection.alias || ast_selection.name
@@ -242,6 +256,7 @@ module GraphQL
242
256
  # If a selection on `node` matches `field_name` (which is backed by `field_defn`)
243
257
  # and matches the `arguments:` constraints, then add that node to `matches`
244
258
  def find_selected_nodes(node, field_name, field_defn, arguments:, matches:)
259
+ return if skipped_by_directive?(node)
245
260
  case node
246
261
  when GraphQL::Language::Nodes::Field
247
262
  if node.name == field_name
@@ -263,7 +278,7 @@ module GraphQL
263
278
  end
264
279
 
265
280
  def arguments_match?(arguments, field_defn, field_node)
266
- query_kwargs = ArgumentHelpers.arguments(@query, nil, field_defn, field_node)
281
+ query_kwargs = ArgumentHelpers.arguments(@query, field_defn, field_node)
267
282
  arguments.all? do |arg_name, arg_value|
268
283
  arg_name = normalize_keyword(arg_name)
269
284
  # Make sure the constraint is present with a matching value
@@ -275,20 +290,15 @@ module GraphQL
275
290
  module ArgumentHelpers
276
291
  module_function
277
292
 
278
- def arguments(query, graphql_object, arg_owner, ast_node)
293
+ def arguments(query, arg_owner, ast_node)
279
294
  kwarg_arguments = {}
280
295
  arg_defns = arg_owner.arguments
281
296
  ast_node.arguments.each do |arg|
282
297
  arg_defn = arg_defns[arg.name] || raise("Invariant: missing argument definition for #{arg.name.inspect} in #{arg_defns.keys} from #{arg_owner}")
283
298
  # Need to distinguish between client-provided `nil`
284
299
  # and nothing-at-all
285
- is_present, value = arg_to_value(query, graphql_object, arg_defn.type, arg.value)
300
+ is_present, value = arg_to_value(query, arg_defn.type, arg.value)
286
301
  if is_present
287
- # This doesn't apply to directives, which are legacy
288
- # Can remove this when Skip and Include use classes or something.
289
- if graphql_object
290
- value = arg_defn.prepare_value(graphql_object, value)
291
- end
292
302
  kwarg_arguments[arg_defn.keyword] = value
293
303
  end
294
304
  end
@@ -305,7 +315,7 @@ module GraphQL
305
315
  # @param arg_type [Class, GraphQL::Schema::NonNull, GraphQL::Schema::List]
306
316
  # @param ast_value [GraphQL::Language::Nodes::VariableIdentifier, String, Integer, Float, Boolean]
307
317
  # @return [Array(is_present, value)]
308
- def arg_to_value(query, graphql_object, arg_type, ast_value)
318
+ def arg_to_value(query, arg_type, ast_value)
309
319
  if ast_value.is_a?(GraphQL::Language::Nodes::VariableIdentifier)
310
320
  # If it's not here, it will get added later
311
321
  if query.variables.key?(ast_value.name)
@@ -316,13 +326,13 @@ module GraphQL
316
326
  elsif ast_value.is_a?(GraphQL::Language::Nodes::NullValue)
317
327
  return true, nil
318
328
  elsif arg_type.is_a?(GraphQL::Schema::NonNull)
319
- arg_to_value(query, graphql_object, arg_type.of_type, ast_value)
329
+ arg_to_value(query, arg_type.of_type, ast_value)
320
330
  elsif arg_type.is_a?(GraphQL::Schema::List)
321
331
  # Treat a single value like a list
322
332
  arg_value = Array(ast_value)
323
333
  list = []
324
334
  arg_value.map do |inner_v|
325
- _present, value = arg_to_value(query, graphql_object, arg_type.of_type, inner_v)
335
+ _present, value = arg_to_value(query, arg_type.of_type, inner_v)
326
336
  list << value
327
337
  end
328
338
  return true, list
@@ -11,12 +11,32 @@ module GraphQL
11
11
  end
12
12
 
13
13
  def has_previous_page
14
- load_nodes
14
+ if @has_previous_page.nil?
15
+ @has_previous_page = if @after_offset && @after_offset > 0
16
+ true
17
+ elsif last
18
+ # See whether there are any nodes _before_ the current offset.
19
+ # If there _is no_ current offset, then there can't be any nodes before it.
20
+ # Assume that if the offset is positive, there are nodes before the offset.
21
+ limited_nodes
22
+ !(@paged_nodes_offset.nil? || @paged_nodes_offset == 0)
23
+ else
24
+ false
25
+ end
26
+ end
15
27
  @has_previous_page
16
28
  end
17
29
 
18
30
  def has_next_page
19
- load_nodes
31
+ if @has_next_page.nil?
32
+ @has_next_page = if @before_offset && @before_offset > 0
33
+ true
34
+ elsif first
35
+ relation_count(set_limit(sliced_nodes, first + 1)) == first + 1
36
+ else
37
+ false
38
+ end
39
+ end
20
40
  @has_next_page
21
41
  end
22
42
 
@@ -80,37 +100,45 @@ module GraphQL
80
100
  end
81
101
  end
82
102
 
83
- # Populate all the pagination info _once_,
84
- # It doesn't do anything on subsequent calls.
85
- def load_nodes
86
- @nodes ||= begin
103
+ # Apply `before` and `after` to the underlying `items`,
104
+ # returning a new relation.
105
+ def sliced_nodes
106
+ @sliced_nodes ||= begin
87
107
  paginated_nodes = items
88
- after_offset = after && offset_from_cursor(after)
89
- before_offset = before && offset_from_cursor(before)
108
+ @after_offset = after && offset_from_cursor(after)
109
+ @before_offset = before && offset_from_cursor(before)
90
110
 
91
- if after_offset
111
+ if @after_offset
92
112
  previous_offset = relation_offset(items) || 0
93
- paginated_nodes = set_offset(paginated_nodes, previous_offset + after_offset)
113
+ paginated_nodes = set_offset(paginated_nodes, previous_offset + @after_offset)
94
114
  end
95
115
 
96
- if before_offset && after_offset
97
- if after_offset < before_offset
116
+ if @before_offset && @after_offset
117
+ if @after_offset < @before_offset
98
118
  # Get the number of items between the two cursors
99
- space_between = before_offset - after_offset - 1
119
+ space_between = @before_offset - @after_offset - 1
100
120
  paginated_nodes = set_limit(paginated_nodes, space_between)
101
121
  else
102
122
  # TODO I think this is untested
103
123
  # The cursors overextend one another to an empty set
104
124
  paginated_nodes = null_relation(paginated_nodes)
105
125
  end
106
- elsif before_offset
126
+ elsif @before_offset
107
127
  # Use limit to cut off the tail of the relation
108
- paginated_nodes = set_limit(paginated_nodes, before_offset - 1)
128
+ paginated_nodes = set_limit(paginated_nodes, @before_offset - 1)
109
129
  end
110
130
 
111
- sliced_nodes_count = relation_count(paginated_nodes)
131
+ paginated_nodes
132
+ end
133
+ end
134
+
135
+ # Apply `first` and `last` to `sliced_nodes`,
136
+ # returning a new relation
137
+ def limited_nodes
138
+ @limited_nodes ||= begin
139
+ paginated_nodes = sliced_nodes
112
140
 
113
- if first && (relation_limit(paginated_nodes).nil? || relation_limit(paginated_nodes) > first)
141
+ if first && (relation_limit(paginated_nodes).nil? || relation_limit(paginated_nodes) > first) && last.nil?
114
142
  # `first` would create a stricter limit that the one already applied, so add it
115
143
  paginated_nodes = set_limit(paginated_nodes, first)
116
144
  end
@@ -125,27 +153,24 @@ module GraphQL
125
153
  end
126
154
  else
127
155
  # No limit, so get the last items
156
+ sliced_nodes_count = relation_count(@sliced_nodes)
128
157
  offset = (relation_offset(paginated_nodes) || 0) + sliced_nodes_count - [last, sliced_nodes_count].min
129
158
  paginated_nodes = set_offset(paginated_nodes, offset)
130
159
  paginated_nodes = set_limit(paginated_nodes, last)
131
160
  end
132
161
  end
133
162
 
134
- @has_next_page = !!(
135
- (before_offset && before_offset > 0) ||
136
- (first && sliced_nodes_count > first)
137
- )
138
-
139
- @has_previous_page = !!(
140
- (after_offset && after_offset > 0) ||
141
- (last && sliced_nodes_count > last)
142
- )
143
-
144
163
  @paged_nodes_offset = relation_offset(paginated_nodes)
145
- # Return an array so we can consistently use `.index(node)` on it
146
- paginated_nodes.to_a
164
+ paginated_nodes
147
165
  end
148
166
  end
167
+
168
+ # Load nodes after applying first/last/before/after,
169
+ # returns an array of nodes
170
+ def load_nodes
171
+ # Return an array so we can consistently use `.index(node)` on it
172
+ @nodes ||= limited_nodes.to_a
173
+ end
149
174
  end
150
175
  end
151
176
  end
@@ -29,7 +29,6 @@ module GraphQL
29
29
  default_value = ast_variable.default_value
30
30
  provided_value = @provided_variables[variable_name]
31
31
  value_was_provided = @provided_variables.key?(variable_name)
32
-
33
32
  begin
34
33
  validation_result = variable_type.validate_input(provided_value, ctx)
35
34
  if validation_result.valid?
@@ -7,6 +7,9 @@ module GraphQL
7
7
  desc "Get the checksum of a graphql-pro version and compare it to published versions on GitHub and graphql-ruby.org"
8
8
  task "graphql:pro:validate", [:gem_version] do |t, args|
9
9
  version = args[:gem_version]
10
+ if version.nil?
11
+ raise ArgumentError, "A specific version is required, eg `rake graphql:pro:validate[1.12.0]`"
12
+ end
10
13
  check = "\e[32m✓\e[0m"
11
14
  ex = "\e[31m✘\e[0m"
12
15
  puts "Validating graphql-pro v#{version}"
@@ -1817,6 +1817,11 @@ module GraphQL
1817
1817
  add_type(t, owner: type, late_types: late_types)
1818
1818
  end
1819
1819
  end
1820
+ if type.kind.interface?
1821
+ type.orphan_types.each do |t|
1822
+ add_type(t, owner: type, late_types: late_types)
1823
+ end
1824
+ end
1820
1825
  if type.kind.object?
1821
1826
  own_possible_types[type.graphql_name] = [type]
1822
1827
  type.interfaces.each do |i|
@@ -60,7 +60,12 @@ module GraphQL
60
60
  end
61
61
 
62
62
  # If false, this part of the query won't be evaluated
63
- def include?(_object, _arguments, _context)
63
+ def include?(_object, arguments, context)
64
+ static_include?(arguments, context)
65
+ end
66
+
67
+ # Determines whether {Execution::Lookahead} considers the field to be selected
68
+ def static_include?(_arguments, _context)
64
69
  true
65
70
  end
66
71
 
@@ -16,7 +16,7 @@ module GraphQL
16
16
 
17
17
  default_directive true
18
18
 
19
- def self.include?(obj, args, ctx)
19
+ def self.static_include?(args, ctx)
20
20
  !!args[:if]
21
21
  end
22
22
  end
@@ -16,7 +16,7 @@ module GraphQL
16
16
 
17
17
  default_directive true
18
18
 
19
- def self.include?(obj, args, ctx)
19
+ def self.static_include?(args, ctx)
20
20
  !args[:if]
21
21
  end
22
22
  end
@@ -689,7 +689,7 @@ module GraphQL
689
689
  # Written iteratively to avoid big stack traces.
690
690
  # @return [Object] Whatever the
691
691
  def with_extensions(obj, args, ctx)
692
- if @extensions.nil?
692
+ if @extensions.empty?
693
693
  yield(obj, args)
694
694
  else
695
695
  # Save these so that the originals can be re-given to `after_resolve` handlers.
@@ -185,11 +185,15 @@ module GraphQL
185
185
  end
186
186
 
187
187
  def coerce_input(value, ctx)
188
+ if value.nil?
189
+ return nil
190
+ end
188
191
  input_values = {}
189
192
 
190
193
  arguments.each do |name, argument_defn|
191
194
  arg_key = argument_defn.keyword
192
195
  has_value = false
196
+
193
197
  # Accept either string or symbol
194
198
  field_value = if value.key?(name)
195
199
  has_value = true
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "1.10.0"
3
+ VERSION = "1.10.1"
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.10.0
4
+ version: 1.10.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Mosolgo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-01-20 00:00:00.000000000 Z
11
+ date: 2020-01-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: benchmark-ips