graphql 1.10.4 → 1.10.5

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