graphql 1.10.4 → 1.10.5

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: abb84b7f4f94ae78b7d23d8726590780465d0018a3b225fb9b0cefbd557e6c1b
4
- data.tar.gz: 78222841f7996f8449081d272169e6bd7e120f5937a08ea04a33c04217ff21dd
3
+ metadata.gz: ace6be63c92b2a2139381664968aef4eeed01a5b1e3936af31ef3b0d7735b944
4
+ data.tar.gz: 6d572562697f474dc82f81e3c28e9d234bf9348ef2446db41a10fa4b6d52cec4
5
5
  SHA512:
6
- metadata.gz: d935f76873acb5bf0f15f9fa7c6d74a1a8ac5268d4885216698b206c66cda923b5cc269f90cf708ac296bf5d2973f9e45fb52558b5223cc3c345ae2650933e0c
7
- data.tar.gz: 601450b86d9d6e9f9445060c24ba0dc74ce88ab2d69b2551b5e8836b4f02d35a0794e75e32c6db60eaefffc22878c87b07162c77a65b8ea1b5e241d6262881ca
6
+ metadata.gz: 4da367c261c3fd79192aea827be4b0cebc864820d4dba6ed74a3c57d9c7772511bf6b96bdd4316e9186bd751954a964b09bc1e455fed4f687edb958d869f25e5
7
+ data.tar.gz: a5d4c7c349cebf6ce1e064763c47a61e6018a29d4b563ce833390ed5d378f5dd977b884f2a1809a7935e43902a9e33e23682662192cc3ff5ab8079c75c1b5fb6
@@ -109,7 +109,6 @@ require "graphql/introspection"
109
109
 
110
110
  require "graphql/analysis_error"
111
111
  require "graphql/coercion_error"
112
- require "graphql/literal_validation_error"
113
112
  require "graphql/runtime_type_error"
114
113
  require "graphql/invalid_null_error"
115
114
  require "graphql/invalid_name_error"
@@ -21,13 +21,17 @@ module GraphQL
21
21
  # since the lexical binding isn't important.
22
22
  HASH_CHILDREN = ->(h, k) { h[k] = {} }
23
23
 
24
+ attr_reader :field_definition, :response_path, :query
25
+
24
26
  # @param node [Language::Nodes::Field] The AST node; used for providing argument values when necessary
25
27
  # @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
+ # @param query [GraphQL::Query] Used for `query.possible_types`
29
+ # @param response_path [Array<String>] The path to the response key for the field
30
+ def initialize(node, field_definition, query, response_path)
31
+ @node = node
28
32
  @field_definition = field_definition
29
33
  @query = query
30
- @node = node
34
+ @response_path = response_path
31
35
  @scoped_children = nil
32
36
  end
33
37
 
@@ -73,9 +77,9 @@ module GraphQL
73
77
  # `node` and `visitor.field_definition` may appear from a cache,
74
78
  # but I think that's ok. If the arguments _didn't_ match,
75
79
  # 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)]
80
+ complexities_on_type = @complexities_on_type_by_query[visitor.query] ||= [ScopedTypeComplexity.new(nil, nil, query, visitor.response_path)]
77
81
 
78
- complexity = complexities_on_type.last.scoped_children[parent_type][field_key] ||= ScopedTypeComplexity.new(node, visitor.field_definition, visitor.query)
82
+ complexity = complexities_on_type.last.scoped_children[parent_type][field_key] ||= ScopedTypeComplexity.new(node, visitor.field_definition, visitor.query, visitor.response_path)
79
83
  # Push it on the stack.
80
84
  complexities_on_type.push(complexity)
81
85
  end
@@ -89,132 +93,140 @@ module GraphQL
89
93
  complexities_on_type.pop
90
94
  end
91
95
 
96
+ private
97
+
92
98
  # @return [Integer]
93
99
  def max_possible_complexity
94
100
  @complexities_on_type_by_query.reduce(0) do |total, (query, complexities_on_type)|
95
101
  root_complexity = complexities_on_type.last
96
102
  # 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])
103
+ total_complexity_for_query = merged_max_complexity_for_scopes(query, [root_complexity.scoped_children])
98
104
  total + total_complexity_for_query
99
105
  end
100
106
  end
101
107
 
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
108
+ # @param query [GraphQL::Query] Used for `query.possible_types`
109
+ # @param scoped_children_hashes [Array<Hash>] Array of scoped children hashes
110
+ # @return [Integer]
111
+ def merged_max_complexity_for_scopes(query, scoped_children_hashes)
112
+ # Figure out what scopes are possible here.
113
+ # Use a hash, but ignore the values; it's just a fast way to work with the keys.
114
+ all_scopes = {}
115
+ scoped_children_hashes.each do |h|
116
+ all_scopes.merge!(h)
121
117
  end
122
118
 
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)
119
+ # If an abstract scope is present, but _all_ of its concrete types
120
+ # are also in the list, remove it from the list of scopes to check,
121
+ # because every possible type is covered by a concrete type.
122
+ # (That is, there are no remainder types to check.)
123
+ prev_keys = all_scopes.keys
124
+ prev_keys.each do |scope|
125
+ next unless scope.kind.abstract?
126
+
127
+ missing_concrete_types = query.possible_types(scope).select { |t| !all_scopes.key?(t) }
128
+ # This concrete type is possible _only_ as a member of the abstract type.
129
+ # So, attribute to it the complexity which belongs to the abstract type.
130
+ missing_concrete_types.each do |concrete_scope|
131
+ all_scopes[concrete_scope] = all_scopes[scope]
129
132
  end
133
+ all_scopes.delete(scope)
134
+ end
130
135
 
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
- #
136
- # TODO redocument
137
- prev_keys = all_scopes.keys
138
- prev_keys.each do |scope|
139
- if scope.kind.abstract?
140
- missing_concrete_types = query.possible_types(scope).select { |t| !all_scopes.key?(t) }
141
- # This concrete type is possible _only_ as a member of the abstract type.
142
- # So, attribute to it the complexity which belongs to the abstract type.
143
- missing_concrete_types.each do |concrete_scope|
144
- all_scopes[concrete_scope] = all_scopes[scope]
136
+ # This will hold `{ type => int }` pairs, one for each possible branch
137
+ complexity_by_scope = {}
138
+
139
+ # For each scope,
140
+ # find the lexical selections that might apply to it,
141
+ # and gather them together into an array.
142
+ # Then, treat the set of selection hashes
143
+ # as a set and calculate the complexity for them as a unit
144
+ all_scopes.each do |scope, _|
145
+ # These will be the selections on `scope`
146
+ children_for_scope = []
147
+ scoped_children_hashes.each do |sc_h|
148
+ sc_h.each do |inner_scope, children_hash|
149
+ if applies_to?(query, scope, inner_scope)
150
+ children_for_scope << children_hash
145
151
  end
146
- all_scopes.delete(scope)
147
152
  end
148
153
  end
149
154
 
150
- # This will hold `{ type => int }` pairs, one for each possible branch
151
- complexity_by_scope = {}
152
-
153
- # For each scope,
154
- # find the lexical selections that might apply to it,
155
- # and gather them together into an array.
156
- # Then, treat the set of selection hashes
157
- # as a set and calculate the complexity for them as a unit
158
- all_scopes.each do |scope, _|
159
- # These will be the selections on `scope`
160
- children_for_scope = []
161
- scoped_children_hashes.each do |sc_h|
162
- sc_h.each do |inner_scope, children_hash|
163
- if applies_to?(query, scope, inner_scope)
164
- children_for_scope << children_hash
165
- end
166
- end
167
- end
155
+ # Calculate the complexity for `scope`, merging all
156
+ # possible lexical branches.
157
+ complexity_value = merged_max_complexity(query, children_for_scope)
158
+ complexity_by_scope[scope] = complexity_value
159
+ end
168
160
 
169
- # Calculate the complexity for `scope`, merging all
170
- # possible lexical branches.
171
- complexity_value = merged_max_complexity(query, children_for_scope)
172
- complexity_by_scope[scope] = complexity_value
173
- end
161
+ # Return the max complexity among all scopes
162
+ complexity_by_scope.each_value.max
163
+ end
174
164
 
175
- # Return the max complexity among all scopes
176
- complexity_by_scope.each_value.max
165
+ def applies_to?(query, left_scope, right_scope)
166
+ if left_scope == right_scope
167
+ # This can happen when several branches are being analyzed together
168
+ true
169
+ else
170
+ # Check if these two scopes have _any_ types in common.
171
+ possible_right_types = query.possible_types(right_scope)
172
+ possible_left_types = query.possible_types(left_scope)
173
+ !(possible_right_types & possible_left_types).empty?
177
174
  end
175
+ end
178
176
 
179
- # @param children_for_scope [Array<Hash>] An array of `scoped_children[scope]` hashes (`{field_key => complexity}`)
180
- # @return [Integer] Complexity value for all these selections in the current scope
181
- def merged_max_complexity(query, children_for_scope)
182
- all_keys = []
183
- children_for_scope.each do |c|
184
- all_keys.concat(c.keys)
185
- end
186
- all_keys.uniq!
187
- complexity_for_keys = {}
188
- all_keys.each do |child_key|
189
-
190
- scoped_children_for_key = nil
191
- complexity_for_key = nil
192
- children_for_scope.each do |children_hash|
193
- if children_hash.key?(child_key)
194
- complexity_for_key = children_hash[child_key]
195
- if complexity_for_key.terminal?
196
- # Assume that all terminals would return the same complexity
197
- # Since it's a terminal, its child complexity is zero.
198
- complexity_for_key = complexity_for_key.own_complexity(0)
199
- complexity_for_keys[child_key] = complexity_for_key
200
- else
201
- scoped_children_for_key ||= []
202
- scoped_children_for_key << complexity_for_key.scoped_children
203
- end
204
- end
205
- end
177
+ # A hook which is called whenever a field's max complexity is calculated.
178
+ # Override this method to capture individual field complexity details.
179
+ #
180
+ # @param scoped_type_complexity [ScopedTypeComplexity]
181
+ # @param max_complexity [Numeric] Field's maximum complexity including child complexity
182
+ # @param child_complexity [Numeric, nil] Field's child complexity
183
+ def field_complexity(scoped_type_complexity, max_complexity:, child_complexity: nil)
184
+ end
206
185
 
207
- if scoped_children_for_key
208
- child_complexity = merged_max_complexity_for_scopes(query, scoped_children_for_key)
209
- # This is the _last_ one we visited; assume it's representative.
210
- max_complexity = complexity_for_key.own_complexity(child_complexity)
211
- complexity_for_keys[child_key] = max_complexity
186
+ # @param children_for_scope [Array<Hash>] An array of `scoped_children[scope]` hashes
187
+ # (`{field_key => complexity}`)
188
+ # @return [Integer] Complexity value for all these selections in the current scope
189
+ def merged_max_complexity(query, children_for_scope)
190
+ all_keys = []
191
+ children_for_scope.each do |c|
192
+ all_keys.concat(c.keys)
193
+ end
194
+ all_keys.uniq!
195
+ complexity_for_keys = {}
196
+
197
+ all_keys.each do |child_key|
198
+ scoped_children_for_key = nil
199
+ complexity_for_key = nil
200
+ children_for_scope.each do |children_hash|
201
+ next unless children_hash.key?(child_key)
202
+
203
+ complexity_for_key = children_hash[child_key]
204
+ if complexity_for_key.terminal?
205
+ # Assume that all terminals would return the same complexity
206
+ # Since it's a terminal, its child complexity is zero.
207
+ complexity = complexity_for_key.own_complexity(0)
208
+ complexity_for_keys[child_key] = complexity
209
+
210
+ field_complexity(complexity_for_key, max_complexity: complexity, child_complexity: nil)
211
+ else
212
+ scoped_children_for_key ||= []
213
+ scoped_children_for_key << complexity_for_key.scoped_children
212
214
  end
213
215
  end
214
216
 
215
- # Calculate the child complexity by summing the complexity of all selections
216
- complexity_for_keys.each_value.inject(0, &:+)
217
+ next unless scoped_children_for_key
218
+
219
+ child_complexity = merged_max_complexity_for_scopes(query, scoped_children_for_key)
220
+ # This is the _last_ one we visited; assume it's representative.
221
+ max_complexity = complexity_for_key.own_complexity(child_complexity)
222
+
223
+ field_complexity(complexity_for_key, max_complexity: max_complexity, child_complexity: child_complexity)
224
+
225
+ complexity_for_keys[child_key] = max_complexity
217
226
  end
227
+
228
+ # Calculate the child complexity by summing the complexity of all selections
229
+ complexity_for_keys.each_value.inject(0, &:+)
218
230
  end
219
231
  end
220
232
  end
@@ -51,7 +51,15 @@ module GraphQL
51
51
 
52
52
  # @return [Hash<Symbol, Object>]
53
53
  def arguments
54
- @arguments ||= @field && ArgumentHelpers.arguments(@query, @field, ast_nodes.first)
54
+ if defined?(@arguments)
55
+ @arguments
56
+ else
57
+ @arguments = if @field
58
+ @query.arguments_for(@ast_nodes.first, @field)
59
+ else
60
+ nil
61
+ end
62
+ end
55
63
  end
56
64
 
57
65
  # True if this node has a selection on `field_name`.
@@ -81,7 +89,7 @@ module GraphQL
81
89
  def selection(field_name, selected_type: @selected_type, arguments: nil)
82
90
  next_field_name = normalize_name(field_name)
83
91
 
84
- next_field_defn = FieldHelpers.get_field(@query.schema, selected_type, next_field_name)
92
+ next_field_defn = get_class_based_field(selected_type, next_field_name)
85
93
  if next_field_defn
86
94
  next_nodes = []
87
95
  @ast_nodes.each do |ast_node|
@@ -127,7 +135,7 @@ module GraphQL
127
135
 
128
136
  subselections_by_type.each do |type, ast_nodes_by_response_key|
129
137
  ast_nodes_by_response_key.each do |response_key, ast_nodes|
130
- field_defn = FieldHelpers.get_field(@query.schema, type, ast_nodes.first.name)
138
+ field_defn = get_class_based_field(type, ast_nodes.first.name)
131
139
  lookahead = Lookahead.new(query: @query, ast_nodes: ast_nodes, field: field_defn, owner_type: type)
132
140
  subselections.push(lookahead)
133
141
  end
@@ -203,12 +211,19 @@ module GraphQL
203
211
  end
204
212
  end
205
213
 
214
+ # Wrap get_field and ensure that it returns a GraphQL::Schema::Field.
215
+ # Remove this when legacy execution is removed.
216
+ def get_class_based_field(type, name)
217
+ f = @query.get_field(type, name)
218
+ f && f.type_class
219
+ end
220
+
206
221
  def skipped_by_directive?(ast_selection)
207
222
  ast_selection.directives.each do |directive|
208
223
  dir_defn = @query.schema.directives.fetch(directive.name)
209
224
  directive_class = dir_defn.type_class
210
225
  if directive_class
211
- dir_args = GraphQL::Execution::Lookahead::ArgumentHelpers.arguments(@query, dir_defn, directive)
226
+ dir_args = @query.arguments_for(directive, dir_defn)
212
227
  return true unless directive_class.static_include?(dir_args, @query.context)
213
228
  end
214
229
  end
@@ -227,7 +242,7 @@ module GraphQL
227
242
  elsif arguments.nil? || arguments.empty?
228
243
  selections_on_type[response_key] = [ast_selection]
229
244
  else
230
- field_defn = FieldHelpers.get_field(@query.schema, selected_type, ast_selection.name)
245
+ field_defn = get_class_based_field(selected_type, ast_selection.name)
231
246
  if arguments_match?(arguments, field_defn, ast_selection)
232
247
  selections_on_type[response_key] = [ast_selection]
233
248
  end
@@ -278,115 +293,13 @@ module GraphQL
278
293
  end
279
294
 
280
295
  def arguments_match?(arguments, field_defn, field_node)
281
- query_kwargs = ArgumentHelpers.arguments(@query, field_defn, field_node)
296
+ query_kwargs = @query.arguments_for(field_node, field_defn)
282
297
  arguments.all? do |arg_name, arg_value|
283
298
  arg_name = normalize_keyword(arg_name)
284
299
  # Make sure the constraint is present with a matching value
285
300
  query_kwargs.key?(arg_name) && query_kwargs[arg_name] == arg_value
286
301
  end
287
302
  end
288
-
289
- # TODO Dedup with interpreter
290
- module ArgumentHelpers
291
- module_function
292
-
293
- def arguments(query, arg_owner, ast_node)
294
- kwarg_arguments = {}
295
- arg_defns = arg_owner.arguments
296
- ast_node.arguments.each do |arg|
297
- arg_defn = arg_defns[arg.name] || raise("Invariant: missing argument definition for #{arg.name.inspect} in #{arg_defns.keys} from #{arg_owner}")
298
- # Need to distinguish between client-provided `nil`
299
- # and nothing-at-all
300
- is_present, value = arg_to_value(query, arg_defn.type, arg.value)
301
- if is_present
302
- kwarg_arguments[arg_defn.keyword] = value
303
- end
304
- end
305
- arg_defns.each do |name, arg_defn|
306
- if arg_defn.default_value? && !kwarg_arguments.key?(arg_defn.keyword)
307
- kwarg_arguments[arg_defn.keyword] = arg_defn.default_value
308
- end
309
- end
310
- kwarg_arguments
311
- end
312
-
313
- # Get a Ruby-ready value from a client query.
314
- # @param graphql_object [Object] The owner of the field whose argument this is
315
- # @param arg_type [Class, GraphQL::Schema::NonNull, GraphQL::Schema::List]
316
- # @param ast_value [GraphQL::Language::Nodes::VariableIdentifier, String, Integer, Float, Boolean]
317
- # @return [Array(is_present, value)]
318
- def arg_to_value(query, arg_type, ast_value)
319
- if ast_value.is_a?(GraphQL::Language::Nodes::VariableIdentifier)
320
- # If it's not here, it will get added later
321
- if query.variables.key?(ast_value.name)
322
- return true, query.variables[ast_value.name]
323
- else
324
- return false, nil
325
- end
326
- elsif ast_value.is_a?(GraphQL::Language::Nodes::NullValue)
327
- return true, nil
328
- elsif arg_type.is_a?(GraphQL::Schema::NonNull)
329
- arg_to_value(query, arg_type.of_type, ast_value)
330
- elsif arg_type.is_a?(GraphQL::Schema::List)
331
- # Treat a single value like a list
332
- arg_value = Array(ast_value)
333
- list = []
334
- arg_value.map do |inner_v|
335
- _present, value = arg_to_value(query, arg_type.of_type, inner_v)
336
- list << value
337
- end
338
- return true, list
339
- elsif arg_type.is_a?(Class) && arg_type < GraphQL::Schema::InputObject
340
- # For these, `prepare` is applied during `#initialize`.
341
- # Pass `nil` so it will be skipped in `#arguments`.
342
- # What a mess.
343
- args = arguments(query, nil, arg_type, ast_value)
344
- # We're not tracking defaults_used, but for our purposes
345
- # we compare the value to the default value.
346
- return true, arg_type.new(ruby_kwargs: args, context: query.context, defaults_used: nil)
347
- else
348
- flat_value = flatten_ast_value(query, ast_value)
349
- return true, arg_type.coerce_input(flat_value, query.context)
350
- end
351
- end
352
-
353
- def flatten_ast_value(query, v)
354
- case v
355
- when GraphQL::Language::Nodes::Enum
356
- v.name
357
- when GraphQL::Language::Nodes::InputObject
358
- h = {}
359
- v.arguments.each do |arg|
360
- h[arg.name] = flatten_ast_value(query, arg.value)
361
- end
362
- h
363
- when Array
364
- v.map { |v2| flatten_ast_value(query, v2) }
365
- when GraphQL::Language::Nodes::VariableIdentifier
366
- flatten_ast_value(query.variables[v.name])
367
- else
368
- v
369
- end
370
- end
371
- end
372
-
373
- # TODO dedup with interpreter
374
- module FieldHelpers
375
- module_function
376
-
377
- def get_field(schema, owner_type, field_name)
378
- field_defn = owner_type.get_field(field_name)
379
- field_defn ||= if owner_type == schema.query.type_class && (entry_point_field = schema.introspection_system.entry_point(name: field_name))
380
- entry_point_field.type_class
381
- elsif (dynamic_field = schema.introspection_system.dynamic_field(name: field_name))
382
- dynamic_field.type_class
383
- else
384
- nil
385
- end
386
-
387
- field_defn
388
- end
389
- end
390
303
  end
391
304
  end
392
305
  end