graphql 1.10.0.pre2 → 1.10.0.pre3

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: 0b0dea1cf870243e0c1ca99c9aa276b3ab6dca48c94048d99033c7e868d6edb7
4
- data.tar.gz: a9067feb798b969d50e6cd3b436432adba4216ad8b66d58cd8cde2d76f9d5589
3
+ metadata.gz: 78e01c4a75da2ab94a931c38e49c2e697de00b28837045807d1c7c9f02a9d831
4
+ data.tar.gz: eb281e483efaafef9824d8e41f0dc0558fe2c2351a4d675d2b875cc2de630a8f
5
5
  SHA512:
6
- metadata.gz: d2effe2d2074b17026c7c6e3ddc71db82b1bd17ce8624dbdd9a88345ff39b2177ae48981deabdda227fb4bcf965469a5e874faeb76c0830d598fdb5501673c98
7
- data.tar.gz: 810111555e1713e4d0d6afabba7102a03f458349d135d92f573236feedd116d81481ba7119c364d9533d39f1d98aa940ce32dc7d452008f6c00827796e5794ef
6
+ metadata.gz: 893b327d435f43a7ab1e97940662a9d786b27446b07280df6fe171e1271cc07048ed1098e7bf6cf7bd643d9c77765be4b1abd175f771faefdcc4cc6e49e0724a
7
+ data.tar.gz: 7ca9736e3fd003b784f2a5c3dfa62a02b44675623c0cddfb4064d26924e77694b6f1f05a9f6ab795d2e6f6ced93f8e57542e4b63123485a7f4599c6d92c36db7
@@ -2,7 +2,7 @@
2
2
 
3
3
  module GraphQL
4
4
  module Execution
5
- # A tracer that wraps query execution with error handling.
5
+ # A plugin that wraps query execution with error handling.
6
6
  # Supports class-based schemas and the new {Interpreter} runtime only.
7
7
  #
8
8
  # @example Handling ActiveRecord::NotFound
@@ -18,31 +18,35 @@ module GraphQL
18
18
  #
19
19
  class Errors
20
20
  def self.use(schema)
21
- schema.tracer(self.new)
21
+ schema.error_handler = self.new(schema)
22
22
  end
23
23
 
24
- def trace(event, data)
25
- case event
26
- when "execute_field", "execute_field_lazy"
27
- with_error_handling(data) { yield }
28
- else
24
+ def initialize(schema)
25
+ @schema = schema
26
+ end
27
+
28
+ class NullErrorHandler
29
+ def self.with_error_handling(_ctx)
29
30
  yield
30
31
  end
31
32
  end
32
33
 
33
- private
34
-
35
- def with_error_handling(trace_data)
34
+ # Call the given block with the schema's configured error handlers.
35
+ #
36
+ # If the block returns a lazy value, it's not wrapped with error handling. That area will have to be wrapped itself.
37
+ #
38
+ # @param ctx [GraphQL::Query::Context]
39
+ # @return [Object] Either the result of the given block, or some object to replace the result, in case of error handling.
40
+ def with_error_handling(ctx)
36
41
  yield
37
42
  rescue StandardError => err
38
- ctx = trace_data[:query].context
39
- schema = ctx.schema
40
- rescues = schema.rescues
43
+ rescues = ctx.schema.rescues
41
44
  _err_class, handler = rescues.find { |err_class, handler| err.is_a?(err_class) }
42
45
  if handler
43
- obj = trace_data[:object]
44
- args = trace_data[:arguments]
45
- field = trace_data[:field]
46
+ runtime_info = ctx.namespace(:interpreter) || {}
47
+ obj = runtime_info[:current_object]
48
+ args = runtime_info[:current_arguments]
49
+ field = runtime_info[:current_field]
46
50
  if obj.is_a?(GraphQL::Schema::Object)
47
51
  obj = obj.object
48
52
  end
@@ -11,7 +11,7 @@ module GraphQL
11
11
  # @return [GraphQL::Query]
12
12
  attr_reader :query
13
13
 
14
- # @return [Class]
14
+ # @return [Class<GraphQL::Schema>]
15
15
  attr_reader :schema
16
16
 
17
17
  # @return [GraphQL::Query::Context]
@@ -43,18 +43,21 @@ module GraphQL
43
43
  # might be stored up in lazies.
44
44
  # @return [void]
45
45
  def run_eager
46
+
46
47
  root_operation = query.selected_operation
47
48
  root_op_type = root_operation.operation_type || "query"
48
49
  root_type = schema.root_type_for_operation(root_op_type)
50
+ path = []
51
+ @interpreter_context[:current_object] = query.root_value
52
+ @interpreter_context[:current_path] = path
49
53
  object_proxy = root_type.authorized_new(query.root_value, context)
50
54
  object_proxy = schema.sync_lazy(object_proxy)
51
55
  if object_proxy.nil?
52
56
  # Root .authorized? returned false.
53
- write_in_response([], nil)
57
+ write_in_response(path, nil)
54
58
  nil
55
59
  else
56
- path = []
57
- evaluate_selections(path, object_proxy, root_type, root_operation.selections, root_operation_type: root_op_type)
60
+ evaluate_selections(path, context.scoped_context, object_proxy, root_type, root_operation.selections, root_operation_type: root_op_type)
58
61
  nil
59
62
  end
60
63
  end
@@ -100,7 +103,8 @@ module GraphQL
100
103
  when GraphQL::Language::Nodes::FragmentSpread
101
104
  fragment_def = query.fragments[node.name]
102
105
  type_defn = schema.get_type(fragment_def.type.name)
103
- query.warden.possible_types(type_defn).each do |t|
106
+ possible_types = query.warden.possible_types(type_defn)
107
+ possible_types.each do |t|
104
108
  if t == owner_type
105
109
  gather_selections(owner_object, owner_type, fragment_def.selections, selections_by_name)
106
110
  break
@@ -112,7 +116,9 @@ module GraphQL
112
116
  end
113
117
  end
114
118
 
115
- def evaluate_selections(path, owner_object, owner_type, selections, root_operation_type: nil)
119
+ def evaluate_selections(path, scoped_context, owner_object, owner_type, selections, root_operation_type: nil)
120
+ @interpreter_context[:current_object] = owner_object
121
+ @interpreter_context[:current_path] = path
116
122
  selections_by_name = {}
117
123
  gather_selections(owner_object, owner_type, selections, selections_by_name)
118
124
  selections_by_name.each do |result_name, field_ast_nodes_or_ast_node|
@@ -155,6 +161,7 @@ module GraphQL
155
161
  @interpreter_context[:current_path] = next_path
156
162
  @interpreter_context[:current_field] = field_defn
157
163
 
164
+ context.scoped_context = scoped_context
158
165
  object = owner_object
159
166
 
160
167
  if is_introspection
@@ -192,6 +199,8 @@ module GraphQL
192
199
  end
193
200
  end
194
201
 
202
+ @interpreter_context[:current_arguments] = kwarg_arguments
203
+
195
204
  # Optimize for the case that field is selected only once
196
205
  if field_ast_nodes.nil? || field_ast_nodes.size == 1
197
206
  next_selections = ast_node.selections
@@ -203,13 +212,15 @@ module GraphQL
203
212
  field_result = resolve_with_directives(object, ast_node) do
204
213
  # Actually call the field resolver and capture the result
205
214
  app_result = begin
206
- query.trace("execute_field", {owner: owner_type, field: field_defn, path: next_path, query: query, object: object, arguments: kwarg_arguments}) do
207
- field_defn.resolve(object, kwarg_arguments, context)
215
+ query.with_error_handling do
216
+ query.trace("execute_field", {owner: owner_type, field: field_defn, path: next_path, query: query, object: object, arguments: kwarg_arguments}) do
217
+ field_defn.resolve(object, kwarg_arguments, context)
218
+ end
208
219
  end
209
220
  rescue GraphQL::ExecutionError => err
210
221
  err
211
222
  end
212
- after_lazy(app_result, owner: owner_type, field: field_defn, path: next_path, owner_object: object, arguments: kwarg_arguments) do |inner_result|
223
+ after_lazy(app_result, owner: owner_type, field: field_defn, path: next_path, scoped_context: context.scoped_context, owner_object: object, arguments: kwarg_arguments) do |inner_result|
213
224
  continue_value = continue_value(next_path, inner_result, field_defn, return_type.non_null?, ast_node)
214
225
  if HALT != continue_value
215
226
  continue_field(next_path, continue_value, field_defn, return_type, ast_node, next_selections, false, object, kwarg_arguments)
@@ -283,7 +294,7 @@ module GraphQL
283
294
  r
284
295
  when "UNION", "INTERFACE"
285
296
  resolved_type_or_lazy = query.resolve_type(type, value)
286
- after_lazy(resolved_type_or_lazy, owner: type, path: path, field: field, owner_object: owner_object, arguments: arguments) do |resolved_type|
297
+ after_lazy(resolved_type_or_lazy, owner: type, path: path, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments) do |resolved_type|
287
298
  possible_types = query.possible_types(type)
288
299
 
289
300
  if !possible_types.include?(resolved_type)
@@ -302,12 +313,12 @@ module GraphQL
302
313
  rescue GraphQL::ExecutionError => err
303
314
  err
304
315
  end
305
- after_lazy(object_proxy, owner: type, path: path, field: field, owner_object: owner_object, arguments: arguments) do |inner_object|
316
+ after_lazy(object_proxy, owner: type, path: path, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments) do |inner_object|
306
317
  continue_value = continue_value(path, inner_object, field, is_non_null, ast_node)
307
318
  if HALT != continue_value
308
319
  response_hash = {}
309
320
  write_in_response(path, response_hash)
310
- evaluate_selections(path, continue_value, type, next_selections)
321
+ evaluate_selections(path, context.scoped_context, continue_value, type, next_selections)
311
322
  response_hash
312
323
  end
313
324
  end
@@ -316,6 +327,7 @@ module GraphQL
316
327
  write_in_response(path, response_list)
317
328
  inner_type = type.of_type
318
329
  idx = 0
330
+ scoped_context = context.scoped_context
319
331
  value.each do |inner_value|
320
332
  next_path = path.dup
321
333
  next_path << idx
@@ -323,7 +335,7 @@ module GraphQL
323
335
  idx += 1
324
336
  set_type_at_path(next_path, inner_type)
325
337
  # This will update `response_list` with the lazy
326
- after_lazy(inner_value, owner: inner_type, path: next_path, field: field, owner_object: owner_object, arguments: arguments) do |inner_inner_value|
338
+ after_lazy(inner_value, owner: inner_type, path: next_path, scoped_context: scoped_context, field: field, owner_object: owner_object, arguments: arguments) do |inner_inner_value|
327
339
  # reset `is_non_null` here and below, because the inner type will have its own nullability constraint
328
340
  continue_value = continue_value(next_path, inner_inner_value, field, false, ast_node)
329
341
  if HALT != continue_value
@@ -379,23 +391,30 @@ module GraphQL
379
391
  # @param field [GraphQL::Schema::Field]
380
392
  # @param eager [Boolean] Set to `true` for mutation root fields only
381
393
  # @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.
382
- def after_lazy(lazy_obj, owner:, field:, path:, owner_object:, arguments:, eager: false)
394
+ def after_lazy(lazy_obj, owner:, field:, path:, scoped_context:, owner_object:, arguments:, eager: false)
395
+ @interpreter_context[:current_object] = owner_object
396
+ @interpreter_context[:current_arguments] = arguments
383
397
  @interpreter_context[:current_path] = path
384
398
  @interpreter_context[:current_field] = field
385
399
  if schema.lazy?(lazy_obj)
386
400
  lazy = GraphQL::Execution::Lazy.new(path: path, field: field) do
387
401
  @interpreter_context[:current_path] = path
388
402
  @interpreter_context[:current_field] = field
403
+ @interpreter_context[:current_object] = owner_object
404
+ @interpreter_context[:current_arguments] = arguments
405
+ context.scoped_context = scoped_context
389
406
  # Wrap the execution of _this_ method with tracing,
390
407
  # but don't wrap the continuation below
391
408
  inner_obj = begin
392
- query.trace("execute_field_lazy", {owner: owner, field: field, path: path, query: query, object: owner_object, arguments: arguments}) do
393
- schema.sync_lazy(lazy_obj)
409
+ query.with_error_handling do
410
+ query.trace("execute_field_lazy", {owner: owner, field: field, path: path, query: query, object: owner_object, arguments: arguments}) do
411
+ schema.sync_lazy(lazy_obj)
412
+ end
394
413
  end
395
414
  rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => err
396
415
  yield(err)
397
416
  end
398
- after_lazy(inner_obj, owner: owner, field: field, path: path, owner_object: owner_object, arguments: arguments, eager: eager) do |really_inner_obj|
417
+ after_lazy(inner_obj, owner: owner, field: field, path: path, scoped_context: context.scoped_context, owner_object: owner_object, arguments: arguments, eager: eager) do |really_inner_obj|
399
418
  yield(really_inner_obj)
400
419
  end
401
420
  end
@@ -494,15 +513,21 @@ module GraphQL
494
513
  if ast_value.nil?
495
514
  return false, nil
496
515
  else
497
- return true, arg_type.new(ruby_kwargs: ast_value, context: context, defaults_used: nil)
516
+ args = ast_value
498
517
  end
499
518
  else
500
519
  # For these, `prepare` is applied during `#initialize`.
501
520
  # Pass `nil` so it will be skipped in `#arguments`.
502
521
  # What a mess.
503
522
  args = arguments(nil, arg_type, ast_value)
504
- return true, arg_type.new(ruby_kwargs: args, context: context, defaults_used: nil)
505
523
  end
524
+
525
+ input_obj = query.with_error_handling do
526
+ # We're not tracking defaults_used, but for our purposes
527
+ # we compare the value to the default value.
528
+ arg_type.new(ruby_kwargs: args, context: context, defaults_used: nil)
529
+ end
530
+ return true, input_obj
506
531
  else
507
532
  flat_value = if already_arguments
508
533
  # It was coerced by variable handling
@@ -15,6 +15,7 @@ module GraphQL
15
15
  end
16
16
 
17
17
  def __type(name:)
18
+ return unless context.warden.reachable_type?(name)
18
19
  type = context.warden.get_type(name)
19
20
 
20
21
  if type && context.interpreter? && !type.is_a?(Module)
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module GraphQL
3
4
  module Introspection
4
5
  class SchemaType < Introspection::BaseObject
@@ -14,7 +15,7 @@ module GraphQL
14
15
  field :directives, [GraphQL::Schema::LateBoundType.new("__Directive")], "A list of all directives supported by this server.", null: false
15
16
 
16
17
  def types
17
- @context.warden.types.values
18
+ @context.warden.reachable_types.sort_by(&:graphql_name)
18
19
  end
19
20
 
20
21
  def query_type
@@ -306,6 +306,13 @@ module GraphQL
306
306
  with_prepared_ast { @subscription }
307
307
  end
308
308
 
309
+ # @api private
310
+ def with_error_handling
311
+ schema.error_handler.with_error_handling(context) do
312
+ yield
313
+ end
314
+ end
315
+
309
316
  private
310
317
 
311
318
  def find_operation(operations, operation_name)
@@ -155,6 +155,7 @@ module GraphQL
155
155
  @path = []
156
156
  @value = nil
157
157
  @context = self # for SharedMethods
158
+ @scoped_context = {}
158
159
  end
159
160
 
160
161
  # @api private
@@ -163,15 +164,30 @@ module GraphQL
163
164
  # @api private
164
165
  attr_writer :value
165
166
 
166
- def_delegators :@provided_values, :[], :[]=, :to_h, :to_hash, :key?, :fetch, :dig
167
- def_delegators :@query, :trace, :interpreter?
167
+ # @api private
168
+ attr_accessor :scoped_context
168
169
 
169
- # @!method [](key)
170
- # Lookup `key` from the hash passed to {Schema#execute} as `context:`
170
+ def_delegators :@provided_values, :[]=
171
+ def_delegators :to_h, :fetch, :dig
172
+ def_delegators :@query, :trace, :interpreter?
171
173
 
172
174
  # @!method []=(key, value)
173
175
  # Reassign `key` to the hash passed to {Schema#execute} as `context:`
174
176
 
177
+ # Lookup `key` from the hash passed to {Schema#execute} as `context:`
178
+ def [](key)
179
+ return @scoped_context[key] if @scoped_context.key?(key)
180
+ @provided_values[key]
181
+ end
182
+
183
+ def to_h
184
+ @provided_values.merge(@scoped_context)
185
+ end
186
+ alias :to_hash :to_h
187
+
188
+ def key?(key)
189
+ @scoped_context.key?(key) || @provided_values.key?(key)
190
+ end
175
191
 
176
192
  # @return [GraphQL::Schema::Warden]
177
193
  def warden
@@ -195,6 +211,15 @@ module GraphQL
195
211
  @value = nil
196
212
  end
197
213
 
214
+ def scoped_merge!(hash)
215
+ @scoped_context = @scoped_context.merge(hash)
216
+ end
217
+
218
+ def scoped_set!(key, value)
219
+ scoped_merge!(key => value)
220
+ nil
221
+ end
222
+
198
223
  class FieldResolutionContext
199
224
  include SharedMethods
200
225
  include Tracing::Traceable
@@ -21,9 +21,13 @@ module GraphQL
21
21
  )
22
22
  end
23
23
 
24
+ def [](key); end
25
+
24
26
  class << self
25
27
  extend Forwardable
26
28
 
29
+ def [](key); end
30
+
27
31
  def instance
28
32
  @instance = self.new
29
33
  end
@@ -38,7 +38,9 @@ module GraphQL
38
38
  memo[variable_name] = if provided_value.nil?
39
39
  nil
40
40
  else
41
- variable_type.coerce_input(provided_value, ctx)
41
+ schema.error_handler.with_error_handling(context) do
42
+ variable_type.coerce_input(provided_value, ctx)
43
+ end
42
44
  end
43
45
  elsif default_value != nil
44
46
  # Add the variable if it wasn't provided but it has a default value (including `null`)
@@ -41,6 +41,7 @@ require "graphql/schema/directive/include"
41
41
  require "graphql/schema/directive/skip"
42
42
  require "graphql/schema/directive/feature"
43
43
  require "graphql/schema/directive/transform"
44
+ require "graphql/schema/type_membership"
44
45
 
45
46
  require "graphql/schema/resolver"
46
47
  require "graphql/schema/mutation"
@@ -466,10 +467,11 @@ module GraphQL
466
467
 
467
468
  # @see [GraphQL::Schema::Warden] Restricted access to members of a schema
468
469
  # @param type_defn [GraphQL::InterfaceType, GraphQL::UnionType] the type whose members you want to retrieve
470
+ # @param context [GraphQL::Query::Context] The context for the current query
469
471
  # @return [Array<GraphQL::ObjectType>] types which belong to `type_defn` in this schema
470
- def possible_types(type_defn)
472
+ def possible_types(type_defn, context = GraphQL::Query::NullContext)
471
473
  @possible_types ||= GraphQL::Schema::PossibleTypes.new(self)
472
- @possible_types.possible_types(type_defn)
474
+ @possible_types.possible_types(type_defn, context)
473
475
  end
474
476
 
475
477
  # @see [GraphQL::Schema::Warden] Resticted access to root types
@@ -615,6 +617,7 @@ module GraphQL
615
617
  alias :_schema_class :class
616
618
  def_delegators :_schema_class, :unauthorized_object, :unauthorized_field, :inaccessible_fields
617
619
  def_delegators :_schema_class, :directive
620
+ def_delegators :_schema_class, :error_handler
618
621
 
619
622
 
620
623
  # Given this schema member, find the class-based definition object
@@ -987,11 +990,20 @@ module GraphQL
987
990
  # @param type [Module] The type definition whose possible types you want to see
988
991
  # @return [Hash<String, Module>] All possible types, if no `type` is given.
989
992
  # @return [Array<Module>] Possible types for `type`, if it's given.
990
- def possible_types(type = nil)
993
+ def possible_types(type = nil, context = GraphQL::Query::NullContext)
991
994
  if type
992
- own_possible_types[type.graphql_name] ||
993
- find_inherited_value(:possible_types, EMPTY_HASH)[type.graphql_name] ||
994
- EMPTY_ARRAY
995
+ # TODO duck-typing `.possible_types` would probably be nicer here
996
+ if type.kind.union?
997
+ type.possible_types(context: context)
998
+ else
999
+ own_possible_types[type.graphql_name] ||
1000
+ introspection_system.possible_types[type.graphql_name] ||
1001
+ (
1002
+ superclass.respond_to?(:possible_types) ?
1003
+ superclass.possible_types(type, context) :
1004
+ EMPTY_ARRAY
1005
+ )
1006
+ end
995
1007
  else
996
1008
  find_inherited_value(:possible_types, EMPTY_HASH)
997
1009
  .merge(own_possible_types)
@@ -1347,6 +1359,12 @@ module GraphQL
1347
1359
  def parse_error(parse_err, ctx)
1348
1360
  ctx.errors.push(parse_err)
1349
1361
  end
1362
+ attr_writer :error_handler
1363
+
1364
+ # @return [GraphQL::Execution::Errors, Class<GraphQL::Execution::Errors::NullErrorHandler>]
1365
+ def error_handler
1366
+ @error_handler ||= GraphQL::Execution::Errors::NullErrorHandler
1367
+ end
1350
1368
 
1351
1369
  def lazy_resolve(lazy_class, value_method)
1352
1370
  lazy_methods.set(lazy_class, value_method)
@@ -1641,18 +1659,16 @@ module GraphQL
1641
1659
  if owner.kind.union?
1642
1660
  # It's a union with possible_types
1643
1661
  # Replace the item by class name
1644
- new_possible_types = owner.possible_types.map { |t|
1645
- if t.is_a?(String) && (t == type.name)
1662
+ owner.type_memberships.each { |tm|
1663
+ possible_type = tm.object_type
1664
+ if possible_type.is_a?(String) && (possible_type == type.name)
1646
1665
  # This is a match of Ruby class names, not graphql names,
1647
1666
  # since strings are used to refer to constants.
1648
- type
1649
- elsif t.is_a?(LateBoundType) && t.graphql_name == type.graphql_name
1650
- type
1651
- else
1652
- t
1667
+ tm.object_type = type
1668
+ elsif possible_type.is_a?(LateBoundType) && possible_type.graphql_name == type.graphql_name
1669
+ tm.object_type = type
1653
1670
  end
1654
1671
  }
1655
- owner.possible_types(*new_possible_types)
1656
1672
  own_possible_types[owner.graphql_name] = owner.possible_types
1657
1673
  elsif type.kind.interface? && owner.kind.object?
1658
1674
  new_interfaces = owner.interfaces.map do |t|
@@ -2,6 +2,10 @@
2
2
  module GraphQL
3
3
  class Schema
4
4
  class Argument
5
+ if !String.method_defined?(:-@)
6
+ using GraphQL::StringDedupBackport
7
+ end
8
+
5
9
  include GraphQL::Schema::Member::CachedGraphQLDefinition
6
10
  include GraphQL::Schema::Member::AcceptsDefinition
7
11
  include GraphQL::Schema::Member::HasPath
@@ -43,8 +47,7 @@ module GraphQL
43
47
  # @param method_access [Boolean] If false, don't build method access on legacy {Query::Arguments} instances.
44
48
  def initialize(arg_name = nil, type_expr = nil, desc = nil, required:, type: nil, name: nil, loads: nil, description: nil, ast_node: nil, default_value: NO_DEFAULT, as: nil, from_resolver: false, camelize: true, prepare: nil, method_access: true, owner:, &definition_block)
45
49
  arg_name ||= name
46
- name_str = camelize ? Member::BuildType.camelize(arg_name.to_s) : arg_name.to_s
47
- @name = name_str.freeze
50
+ @name = -(camelize ? Member::BuildType.camelize(arg_name.to_s) : arg_name.to_s)
48
51
  @type_expr = type_expr || type
49
52
  @description = desc || description
50
53
  @null = !required
@@ -10,8 +10,9 @@ module Base64Bp
10
10
  module_function
11
11
 
12
12
  def urlsafe_encode64(bin, padding:)
13
- str = strict_encode64(bin).tr("+/", "-_")
14
- str = str.delete("=") unless padding
13
+ str = strict_encode64(bin)
14
+ str.tr!("+/", "-_")
15
+ str.delete!("=") unless padding
15
16
  str
16
17
  end
17
18
 
@@ -16,6 +16,7 @@ module GraphQL
16
16
  include GraphQL::Schema::Member::HasAstNode
17
17
  include GraphQL::Schema::Member::HasPath
18
18
  extend GraphQL::Schema::FindInheritedValue
19
+ include GraphQL::Schema::FindInheritedValue::EmptyObjects
19
20
 
20
21
  # @return [String] the GraphQL name for this field, camelized unless `camelize: false` is provided
21
22
  attr_reader :name
@@ -186,7 +187,7 @@ module GraphQL
186
187
  # @param trace [Boolean] If true, a {GraphQL::Tracing} tracer will measure this scalar field
187
188
  # @param ast_node [Language::Nodes::FieldDefinition, nil] If this schema was parsed from definition, this AST node defined the field
188
189
  # @param method_conflict_warning [Boolean] If false, skip the warning if this field's method conflicts with a built-in method
189
- def initialize(type: nil, name: nil, owner: nil, null: nil, field: nil, function: nil, description: nil, deprecation_reason: nil, method: nil, hash_key: nil, resolver_method: nil, resolve: nil, connection: nil, max_page_size: nil, scope: nil, introspection: false, camelize: true, trace: nil, complexity: 1, ast_node: nil, extras: [], extensions: [], resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, arguments: {}, &definition_block)
190
+ def initialize(type: nil, name: nil, owner: nil, null: nil, field: nil, function: nil, description: nil, deprecation_reason: nil, method: nil, hash_key: nil, resolver_method: nil, resolve: nil, connection: nil, max_page_size: nil, scope: nil, introspection: false, camelize: true, trace: nil, complexity: 1, ast_node: nil, extras: [], extensions: EMPTY_ARRAY, resolver_class: nil, subscription_scope: nil, relay_node_field: false, relay_nodes_field: false, method_conflict_warning: true, arguments: EMPTY_HASH, &definition_block)
190
191
  if name.nil?
191
192
  raise ArgumentError, "missing first `name` argument or keyword `name:`"
192
193
  end
@@ -202,8 +203,9 @@ module GraphQL
202
203
  raise ArgumentError, "keyword `extras:` may only be used with method-based resolve and class-based field such as mutation class, please remove `field:`, `function:` or `resolve:`"
203
204
  end
204
205
  @original_name = name
205
- @underscored_name = -Member::BuildType.underscore(name.to_s)
206
- @name = -(camelize ? Member::BuildType.camelize(name.to_s) : name.to_s)
206
+ name_s = -name.to_s
207
+ @underscored_name = -Member::BuildType.underscore(name_s)
208
+ @name = -(camelize ? Member::BuildType.camelize(name_s) : name_s)
207
209
  @description = description
208
210
  if field.is_a?(GraphQL::Schema::Field)
209
211
  raise ArgumentError, "Instead of passing a field as `field:`, use `add_field(field)` to add an already-defined field."
@@ -250,13 +252,11 @@ module GraphQL
250
252
  @ast_node = ast_node
251
253
  @method_conflict_warning = method_conflict_warning
252
254
 
253
- # Override the default from HasArguments
254
- @own_arguments = {}
255
255
  arguments.each do |name, arg|
256
256
  if arg.is_a?(Hash)
257
257
  argument(name: name, **arg)
258
258
  else
259
- @own_arguments[name] = arg
259
+ own_arguments[name] = arg
260
260
  end
261
261
  end
262
262
 
@@ -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.empty?
692
+ if @extensions.nil?
693
693
  yield(obj, args)
694
694
  else
695
695
  # Save these so that the originals can be re-given to `after_resolve` handlers.
@@ -1,5 +1,4 @@
1
1
  # frozen_string_literal: true
2
- require 'irb/ruby-token'
3
2
 
4
3
  module GraphQL
5
4
  class Schema
@@ -43,9 +42,7 @@ module GraphQL
43
42
  # A list of Ruby keywords.
44
43
  #
45
44
  # @api private
46
- RUBY_KEYWORDS = RubyToken::TokenDefinitions.select { |definition| definition[1] == RubyToken::TkId }
47
- .map { |definition| definition[2] }
48
- .compact
45
+ RUBY_KEYWORDS = [:class, :module, :def, :undef, :begin, :rescue, :ensure, :end, :if, :unless, :then, :elsif, :else, :case, :when, :while, :until, :for, :break, :next, :redo, :retry, :in, :do, :return, :yield, :super, :self, :nil, :true, :false, :and, :or, :not, :alias, :defined?, :BEGIN, :END, :__LINE__, :__FILE__]
49
46
 
50
47
  # A list of GraphQL-Ruby keywords.
51
48
  #
@@ -35,10 +35,12 @@ module GraphQL
35
35
  # @return [GraphQL::Schema::Object, GraphQL::Execution::Lazy]
36
36
  # @raise [GraphQL::UnauthorizedError] if the user-provided hook returns `false`
37
37
  def authorized_new(object, context)
38
- auth_val = begin
39
- authorized?(object, context)
40
- rescue GraphQL::UnauthorizedError => err
41
- context.schema.unauthorized_object(err)
38
+ auth_val = context.query.with_error_handling do
39
+ begin
40
+ authorized?(object, context)
41
+ rescue GraphQL::UnauthorizedError => err
42
+ context.schema.unauthorized_object(err)
43
+ end
42
44
  end
43
45
 
44
46
  context.schema.after_lazy(auth_val) do |is_authorized|
@@ -20,12 +20,12 @@ module GraphQL
20
20
  end
21
21
  end
22
22
 
23
- def possible_types(type_defn)
23
+ def possible_types(type_defn, ctx)
24
24
  case type_defn
25
25
  when Module
26
- possible_types(type_defn.graphql_definition)
26
+ possible_types(type_defn.graphql_definition, ctx)
27
27
  when GraphQL::UnionType
28
- type_defn.possible_types
28
+ type_defn.possible_types(ctx)
29
29
  when GraphQL::InterfaceType
30
30
  @interface_implementers[type_defn]
31
31
  when GraphQL::BaseType
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ class Schema
5
+ # This class joins an object type to an abstract type (interface or union) of which
6
+ # it is a member.
7
+ #
8
+ # TODO: Not yet implemented for interfaces.
9
+ class TypeMembership
10
+ # @return [Class<GraphQL::Schema::Object>]
11
+ attr_accessor :object_type
12
+
13
+ # @return [Class<GraphQL::Schema::Union>, Module<GraphQL::Schema::Interface>]
14
+ attr_reader :abstract_type
15
+
16
+ # Called when an object is hooked up to an abstract type, such as {Schema::Union.possible_types}
17
+ # or {Schema::Object.implements} (for interfaces).
18
+ #
19
+ # @param abstract_type [Class<GraphQL::Schema::Union>, Module<GraphQL::Schema::Interface>]
20
+ # @param object_type [Class<GraphQL::Schema::Object>]
21
+ # @param options [Hash] Any options passed to `.possible_types` or `.implements`
22
+ def initialize(abstract_type, object_type, options)
23
+ @abstract_type = abstract_type
24
+ @object_type = object_type
25
+ @options = options
26
+ end
27
+
28
+ # @return [Boolean] if false, {#object_type} will be treated as _not_ a member of {#abstract_type}
29
+ def visible?(_ctx)
30
+ true
31
+ end
32
+ end
33
+ end
34
+ end
@@ -5,13 +5,19 @@ module GraphQL
5
5
  extend GraphQL::Schema::Member::AcceptsDefinition
6
6
 
7
7
  class << self
8
- def possible_types(*types)
8
+ def possible_types(*types, context: GraphQL::Query::NullContext, **options)
9
9
  if types.any?
10
- @possible_types = types
10
+ types.each do |t|
11
+ type_memberships << type_membership_class.new(self, t, options)
12
+ end
11
13
  else
12
- all_possible_types = @possible_types || []
13
- all_possible_types += super if defined?(super)
14
- all_possible_types.uniq
14
+ visible_types = []
15
+ type_memberships.each do |type_membership|
16
+ if type_membership.visible?(context)
17
+ visible_types << type_membership.object_type
18
+ end
19
+ end
20
+ visible_types
15
21
  end
16
22
  end
17
23
 
@@ -19,8 +25,8 @@ module GraphQL
19
25
  type_defn = GraphQL::UnionType.new
20
26
  type_defn.name = graphql_name
21
27
  type_defn.description = description
22
- type_defn.possible_types = possible_types
23
28
  type_defn.ast_node = ast_node
29
+ type_defn.type_memberships = type_memberships
24
30
  if respond_to?(:resolve_type)
25
31
  type_defn.resolve_type = method(:resolve_type)
26
32
  end
@@ -28,9 +34,21 @@ module GraphQL
28
34
  type_defn
29
35
  end
30
36
 
37
+ def type_membership_class(membership_class = nil)
38
+ if membership_class
39
+ @type_membership_class = membership_class
40
+ else
41
+ @type_membership_class || find_inherited_value(:type_membership_class, GraphQL::Schema::TypeMembership)
42
+ end
43
+ end
44
+
31
45
  def kind
32
46
  GraphQL::TypeKinds::UNION
33
47
  end
48
+
49
+ def type_memberships
50
+ @type_memberships ||= []
51
+ end
34
52
  end
35
53
  end
36
54
  end
@@ -1,4 +1,7 @@
1
1
  # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
2
5
  module GraphQL
3
6
  class Schema
4
7
  # Restrict access to a {GraphQL::Schema} with a user-defined filter.
@@ -44,6 +47,7 @@ module GraphQL
44
47
  @query = @schema.query
45
48
  @mutation = @schema.mutation
46
49
  @subscription = @schema.subscription
50
+ @context = context
47
51
  @visibility_cache = read_through { |m| filter.call(m, context) }
48
52
  end
49
53
 
@@ -74,6 +78,17 @@ module GraphQL
74
78
  @visible_types[type_name]
75
79
  end
76
80
 
81
+ # @return [Array<GraphQL::BaseType>] Visible and reachable types in the schema
82
+ def reachable_types
83
+ @reachable_types ||= reachable_type_set.to_a
84
+ end
85
+
86
+ # @return Boolean True if the type is visible and reachable in the schema
87
+ def reachable_type?(type_name)
88
+ type = get_type(type_name)
89
+ type && reachable_type_set.include?(type)
90
+ end
91
+
77
92
  # @return [GraphQL::Field, nil] The field named `field_name` on `parent_type`, if it exists
78
93
  def get_field(parent_type, field_name)
79
94
 
@@ -93,15 +108,9 @@ module GraphQL
93
108
 
94
109
  # @return [Array<GraphQL::BaseType>] The types which may be member of `type_defn`
95
110
  def possible_types(type_defn)
96
- @visible_possible_types ||= if @schema.is_a?(Class)
97
- all_possible_types = @schema.possible_types
98
- read_through { |type_defn|
99
- pt = all_possible_types[type_defn.graphql_name] || []
100
- pt.select { |t| visible_type?(t) }
101
- }
102
- else
103
- read_through { |type_defn| @schema.possible_types(type_defn).select { |t| visible_type?(t) } }
104
- end
111
+ @visible_possible_types ||= read_through { |type_defn|
112
+ @schema.possible_types(type_defn, @context).select { |t| visible_type?(t) }
113
+ }
105
114
  @visible_possible_types[type_defn]
106
115
  end
107
116
 
@@ -209,6 +218,67 @@ module GraphQL
209
218
  def read_through
210
219
  Hash.new { |h, k| h[k] = yield(k) }
211
220
  end
221
+
222
+ def reachable_type_set
223
+ return @reachable_type_set if defined?(@reachable_type_set)
224
+
225
+ @reachable_type_set = Set.new
226
+
227
+ unvisited_types = []
228
+ ['query', 'mutation', 'subscription'].each do |op_name|
229
+ root_type = root_type_for_operation(op_name)
230
+ unvisited_types << root_type if root_type
231
+ end
232
+ unvisited_types.concat(@schema.introspection_system.types.values)
233
+ @schema.orphan_types.each do |orphan_type|
234
+ if get_type(orphan_type.graphql_name)
235
+ unvisited_types << orphan_type
236
+ end
237
+ end
238
+
239
+ until unvisited_types.empty?
240
+ type = unvisited_types.pop
241
+ if @reachable_type_set.add?(type)
242
+ if type.kind.input_object?
243
+ # recurse into visible arguments
244
+ arguments(type).each do |argument|
245
+ argument_type = argument.type.unwrap
246
+ unvisited_types << argument_type
247
+ end
248
+ elsif type.kind.union?
249
+ # recurse into visible possible types
250
+ possible_types(type).each do |possible_type|
251
+ unvisited_types << possible_type
252
+ end
253
+ elsif type.kind.fields?
254
+ if type.kind.interface?
255
+ # recurse into visible possible types
256
+ possible_types(type).each do |possible_type|
257
+ unvisited_types << possible_type
258
+ end
259
+ elsif type.kind.object?
260
+ # recurse into visible implemented interfaces
261
+ interfaces(type).each do |interface|
262
+ unvisited_types << interface
263
+ end
264
+ end
265
+
266
+ # recurse into visible fields
267
+ fields(type).each do |field|
268
+ field_type = field.type.unwrap
269
+ unvisited_types << field_type
270
+ # recurse into visible arguments
271
+ arguments(field).each do |argument|
272
+ argument_type = argument.type.unwrap
273
+ unvisited_types << argument_type
274
+ end
275
+ end
276
+ end
277
+ end
278
+ end
279
+
280
+ @reachable_type_set
281
+ end
212
282
  end
213
283
  end
214
284
  end
@@ -24,22 +24,27 @@ module GraphQL
24
24
  # }
25
25
  #
26
26
  class UnionType < GraphQL::BaseType
27
- accepts_definitions :possible_types, :resolve_type
28
- ensure_defined :possible_types, :resolve_type, :resolve_type_proc
27
+ accepts_definitions :resolve_type, :type_membership_class,
28
+ possible_types: ->(target, possible_types, options = {}) { target.add_possible_types(possible_types, options) }
29
+ ensure_defined :possible_types, :resolve_type, :resolve_type_proc, :type_membership_class
29
30
 
30
31
  attr_accessor :resolve_type_proc
32
+ attr_reader :type_memberships
33
+ attr_accessor :type_membership_class
31
34
 
32
35
  def initialize
33
36
  super
34
- @dirty_possible_types = []
35
- @clean_possible_types = nil
37
+ @type_membership_class = GraphQL::Schema::TypeMembership
38
+ @type_memberships = []
39
+ @cached_possible_types = nil
36
40
  @resolve_type_proc = nil
37
41
  end
38
42
 
39
43
  def initialize_copy(other)
40
44
  super
41
- @clean_possible_types = nil
42
- @dirty_possible_types = other.dirty_possible_types.dup
45
+ @type_membership_class = other.type_membership_class
46
+ @type_memberships = other.type_memberships.dup
47
+ @cached_possible_types = nil
43
48
  end
44
49
 
45
50
  def kind
@@ -47,26 +52,37 @@ module GraphQL
47
52
  end
48
53
 
49
54
  # @return [Boolean] True if `child_type_defn` is a member of this {UnionType}
50
- def include?(child_type_defn)
51
- possible_types.include?(child_type_defn)
52
- end
53
-
54
- def possible_types=(new_possible_types)
55
- @clean_possible_types = nil
56
- @dirty_possible_types = new_possible_types
55
+ def include?(child_type_defn, ctx = GraphQL::Query::NullContext)
56
+ possible_types(ctx).include?(child_type_defn)
57
57
  end
58
58
 
59
59
  # @return [Array<GraphQL::ObjectType>] Types which may be found in this union
60
- def possible_types
61
- @clean_possible_types ||= begin
62
- if @dirty_possible_types.respond_to?(:map)
63
- @dirty_possible_types.map { |type| GraphQL::BaseType.resolve_related_type(type) }
64
- else
65
- @dirty_possible_types
66
- end
60
+ def possible_types(ctx = GraphQL::Query::NullContext)
61
+ if ctx == GraphQL::Query::NullContext
62
+ # Only cache the default case; if we cached for every `ctx`, it would be a memory leak
63
+ # (The warden should cache calls to this method, so it's called only once per query,
64
+ # unless user code calls it directly.)
65
+ @cached_possible_types ||= possible_types_for_context(ctx)
66
+ else
67
+ possible_types_for_context(ctx)
67
68
  end
68
69
  end
69
70
 
71
+ def possible_types=(types)
72
+ # This is a re-assignment, so clear the previous values
73
+ @type_memberships = []
74
+ @cached_possible_types = nil
75
+ add_possible_types(types, {})
76
+ end
77
+
78
+ def add_possible_types(types, options)
79
+ @type_memberships ||= []
80
+ Array(types).each { |t|
81
+ @type_memberships << self.type_membership_class.new(self, t, options)
82
+ }
83
+ nil
84
+ end
85
+
70
86
  # Get a possible type of this {UnionType} by type name
71
87
  # @param type_name [String]
72
88
  # @param ctx [GraphQL::Query::Context] The context for the current query
@@ -93,8 +109,20 @@ module GraphQL
93
109
  @resolve_type_proc = new_resolve_type_proc
94
110
  end
95
111
 
96
- protected
112
+ def type_memberships=(type_memberships)
113
+ @type_memberships = type_memberships
114
+ end
115
+
116
+ private
97
117
 
98
- attr_reader :dirty_possible_types
118
+ def possible_types_for_context(ctx)
119
+ visible_types = []
120
+ @type_memberships.each do |type_membership|
121
+ if type_membership.visible?(ctx)
122
+ visible_types << BaseType.resolve_related_type(type_membership.object_type)
123
+ end
124
+ end
125
+ visible_types
126
+ end
99
127
  end
100
128
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
- VERSION = "1.10.0.pre2"
3
+ VERSION = "1.10.0.pre3"
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.pre2
4
+ version: 1.10.0.pre3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Mosolgo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-12-04 00:00:00.000000000 Z
11
+ date: 2019-12-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: benchmark-ips
@@ -599,6 +599,7 @@ files:
599
599
  - lib/graphql/schema/timeout_middleware.rb
600
600
  - lib/graphql/schema/traversal.rb
601
601
  - lib/graphql/schema/type_expression.rb
602
+ - lib/graphql/schema/type_membership.rb
602
603
  - lib/graphql/schema/union.rb
603
604
  - lib/graphql/schema/unique_within_type.rb
604
605
  - lib/graphql/schema/validation.rb