graphql 1.10.0 → 1.10.1

Sign up to get free protection for your applications and to get access to all the features.
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