graphql 1.10.0.pre2 → 1.10.0.pre3

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