graphql 1.9.16 → 1.9.21

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql.rb +8 -0
  3. data/lib/graphql/argument.rb +2 -2
  4. data/lib/graphql/define/assign_object_field.rb +2 -2
  5. data/lib/graphql/define/defined_object_proxy.rb +3 -0
  6. data/lib/graphql/define/instance_definable.rb +14 -3
  7. data/lib/graphql/execution/errors.rb +15 -14
  8. data/lib/graphql/execution/execute.rb +1 -1
  9. data/lib/graphql/execution/interpreter/runtime.rb +39 -17
  10. data/lib/graphql/execution/multiplex.rb +3 -3
  11. data/lib/graphql/introspection/entry_points.rb +2 -1
  12. data/lib/graphql/introspection/schema_type.rb +2 -1
  13. data/lib/graphql/language/document_from_schema_definition.rb +9 -3
  14. data/lib/graphql/language/nodes.rb +2 -2
  15. data/lib/graphql/query.rb +7 -1
  16. data/lib/graphql/query/context.rb +31 -9
  17. data/lib/graphql/query/null_context.rb +4 -0
  18. data/lib/graphql/query/variables.rb +3 -1
  19. data/lib/graphql/relay/node.rb +2 -2
  20. data/lib/graphql/schema.rb +58 -7
  21. data/lib/graphql/schema/argument.rb +4 -0
  22. data/lib/graphql/schema/base_64_bp.rb +3 -2
  23. data/lib/graphql/schema/build_from_definition.rb +26 -9
  24. data/lib/graphql/schema/directive.rb +7 -1
  25. data/lib/graphql/schema/introspection_system.rb +4 -1
  26. data/lib/graphql/schema/loader.rb +9 -3
  27. data/lib/graphql/schema/member/has_fields.rb +1 -4
  28. data/lib/graphql/schema/mutation.rb +1 -1
  29. data/lib/graphql/schema/object.rb +6 -4
  30. data/lib/graphql/schema/possible_types.rb +3 -3
  31. data/lib/graphql/schema/relay_classic_mutation.rb +1 -1
  32. data/lib/graphql/schema/resolver.rb +1 -1
  33. data/lib/graphql/schema/subscription.rb +5 -5
  34. data/lib/graphql/schema/type_membership.rb +34 -0
  35. data/lib/graphql/schema/union.rb +26 -6
  36. data/lib/graphql/schema/warden.rb +77 -3
  37. data/lib/graphql/subscriptions.rb +2 -2
  38. data/lib/graphql/subscriptions/subscription_root.rb +10 -2
  39. data/lib/graphql/union_type.rb +58 -23
  40. data/lib/graphql/version.rb +1 -1
  41. metadata +6 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 75a0bed671ca4979601036351265dfb7a0ce502b4f6937938084f2b9efaa7cd3
4
- data.tar.gz: c43fd98af6355bde57ca2e66b1c98831498f3ac71792176063d934f968c1355f
3
+ metadata.gz: 7cc71a5ff7b742541447b08e62f4b0561ae2b7ae3767d9b18000e0cfdcd411a1
4
+ data.tar.gz: decb826d15ed416571e152b108698a326803cadf45cc78d9a823344bc156b3f4
5
5
  SHA512:
6
- metadata.gz: '096f8f6cfa0aa0465f13a54297e10e5564555e5f7937ab1ea2c10d4af49db84b3a4dcf2ca30692aac5b55a6df09e163db62e8e2d712ea3ef7763b20230b642e0'
7
- data.tar.gz: aa41d320f6ecf571c81d9243179fc5ffd7b53da4f12b741670caed08fb9405725a30dd56c1db5b6e6a20985e7e506a7ee2c1548097fb8a35e71e0094fbe307ff
6
+ metadata.gz: ef0ce5989f626b9907fd77c0c54dd42f695e4484236da24f689a160fc521614d7cdc9600f0c9044c2e3b547afc1d862efa23da45b2bc13b0173f3c3af2683059
7
+ data.tar.gz: e45810e9c2e0f885e42bfe90c4daf9383e0860543ac9a6ecf6215f994b7af43c0d8d46b74da78a43f9159e3c9fe3882c5ac97257f67a5272407eb6f9b80bbe87
@@ -7,6 +7,14 @@ require "forwardable"
7
7
  require_relative "./graphql/railtie" if defined? Rails::Railtie
8
8
 
9
9
  module GraphQL
10
+ # forwards-compat for argument handling
11
+ module Ruby2Keywords
12
+ if RUBY_VERSION < "2.7"
13
+ def ruby2_keywords(*)
14
+ end
15
+ end
16
+ end
17
+
10
18
  class Error < StandardError
11
19
  end
12
20
 
@@ -134,9 +134,9 @@ module GraphQL
134
134
  end
135
135
 
136
136
  if type_or_argument.is_a?(GraphQL::Argument)
137
- type_or_argument.redefine(kwargs, &block)
137
+ type_or_argument.redefine(**kwargs, &block)
138
138
  else
139
- GraphQL::Argument.define(kwargs, &block)
139
+ GraphQL::Argument.define(**kwargs, &block)
140
140
  end
141
141
  end
142
142
 
@@ -28,9 +28,9 @@ module GraphQL
28
28
  end
29
29
 
30
30
  obj_field = if base_field
31
- base_field.redefine(kwargs, &block)
31
+ base_field.redefine(**kwargs, &block)
32
32
  else
33
- GraphQL::Field.define(kwargs, &block)
33
+ GraphQL::Field.define(**kwargs, &block)
34
34
  end
35
35
 
36
36
 
@@ -1,10 +1,12 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module GraphQL
3
4
  module Define
4
5
  # This object delegates most methods to a dictionary of functions, {@dictionary}.
5
6
  # {@target} is passed to the specified function, along with any arguments and block.
6
7
  # This allows a method-based DSL without adding methods to the defined class.
7
8
  class DefinedObjectProxy
9
+ extend GraphQL::Ruby2Keywords
8
10
  # The object which will be defined by definition functions
9
11
  attr_reader :target
10
12
 
@@ -32,6 +34,7 @@ module GraphQL
32
34
  end
33
35
 
34
36
  # Lookup a function from the dictionary and call it if it's found.
37
+ ruby2_keywords
35
38
  def method_missing(name, *args, &block)
36
39
  definition = @dictionary[name]
37
40
  if definition
@@ -154,7 +154,12 @@ module GraphQL
154
154
  defn_proxy = DefinedObjectProxy.new(self)
155
155
  # Apply definition from `define(...)` kwargs
156
156
  defn.define_keywords.each do |keyword, value|
157
- defn_proxy.public_send(keyword, value)
157
+ # Don't splat string hashes, which blows up on Rubies before 2.7
158
+ if value.is_a?(Hash) && !value.empty? && value.each_key.all? { |k| k.is_a?(Symbol) }
159
+ defn_proxy.public_send(keyword, **value)
160
+ else
161
+ defn_proxy.public_send(keyword, value)
162
+ end
158
163
  end
159
164
  # and/or apply definition from `define { ... }` block
160
165
  if defn.define_proc
@@ -287,12 +292,18 @@ module GraphQL
287
292
  end
288
293
 
289
294
  class AssignAttribute
295
+ extend GraphQL::Ruby2Keywords
296
+
290
297
  def initialize(attr_name)
291
298
  @attr_assign_method = :"#{attr_name}="
292
299
  end
293
300
 
294
- def call(defn, value)
295
- defn.public_send(@attr_assign_method, value)
301
+ # Even though we're just using the first value here,
302
+ # We have to add a splat here to use `ruby2_keywords`,
303
+ # so that it will accept a `[{}]` input from the caller.
304
+ ruby2_keywords
305
+ def call(defn, *value)
306
+ defn.public_send(@attr_assign_method, value.first)
296
307
  end
297
308
  end
298
309
  end
@@ -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
@@ -19,34 +19,35 @@ module GraphQL
19
19
  class Errors
20
20
  def self.use(schema)
21
21
  schema_class = schema.is_a?(Class) ? schema : schema.target.class
22
- schema.tracer(self.new(schema_class))
22
+ schema_class.error_handler = self.new(schema_class)
23
23
  end
24
24
 
25
25
  def initialize(schema)
26
26
  @schema = schema
27
27
  end
28
28
 
29
- def trace(event, data)
30
- case event
31
- when "execute_field", "execute_field_lazy"
32
- with_error_handling(data) { yield }
33
- else
29
+ class NullErrorHandler
30
+ def self.with_error_handling(_ctx)
34
31
  yield
35
32
  end
36
33
  end
37
34
 
38
- private
39
-
40
- def with_error_handling(trace_data)
35
+ # Call the given block with the schema's configured error handlers.
36
+ #
37
+ # If the block returns a lazy value, it's not wrapped with error handling. That area will have to be wrapped itself.
38
+ #
39
+ # @param ctx [GraphQL::Query::Context]
40
+ # @return [Object] Either the result of the given block, or some object to replace the result, in case of error handling.
41
+ def with_error_handling(ctx)
41
42
  yield
42
43
  rescue StandardError => err
43
44
  rescues = @schema.rescues
44
45
  _err_class, handler = rescues.find { |err_class, handler| err.is_a?(err_class) }
45
46
  if handler
46
- obj = trace_data[:object]
47
- args = trace_data[:arguments]
48
- ctx = trace_data[:query].context
49
- field = trace_data[:field]
47
+ runtime_info = ctx.namespace(:interpreter) || {}
48
+ obj = runtime_info[:current_object]
49
+ args = runtime_info[:current_arguments]
50
+ field = runtime_info[:current_field]
50
51
  if obj.is_a?(GraphQL::Schema::Object)
51
52
  obj = obj.object
52
53
  end
@@ -20,7 +20,7 @@ module GraphQL
20
20
 
21
21
  def execute(ast_operation, root_type, query)
22
22
  result = resolve_root_selection(query)
23
- lazy_resolve_root_selection(result, {query: query})
23
+ lazy_resolve_root_selection(result, **{query: query})
24
24
  GraphQL::Execution::Flatten.call(query.context)
25
25
  end
26
26
 
@@ -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,19 +43,22 @@ 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
  legacy_root_type = schema.root_type_for_operation(root_op_type)
49
50
  root_type = legacy_root_type.metadata[:type_class] || raise("Invariant: type must be class-based: #{legacy_root_type}")
51
+ path = []
52
+ @interpreter_context[:current_object] = query.root_value
53
+ @interpreter_context[:current_path] = path
50
54
  object_proxy = root_type.authorized_new(query.root_value, context)
51
55
  object_proxy = schema.sync_lazy(object_proxy)
52
56
  if object_proxy.nil?
53
57
  # Root .authorized? returned false.
54
- write_in_response([], nil)
58
+ write_in_response(path, nil)
55
59
  nil
56
60
  else
57
- path = []
58
- evaluate_selections(path, object_proxy, root_type, root_operation.selections, root_operation_type: root_op_type)
61
+ evaluate_selections(path, context.scoped_context, object_proxy, root_type, root_operation.selections, root_operation_type: root_op_type)
59
62
  nil
60
63
  end
61
64
  end
@@ -115,7 +118,9 @@ module GraphQL
115
118
  end
116
119
  end
117
120
 
118
- def evaluate_selections(path, owner_object, owner_type, selections, root_operation_type: nil)
121
+ def evaluate_selections(path, scoped_context, owner_object, owner_type, selections, root_operation_type: nil)
122
+ @interpreter_context[:current_object] = owner_object
123
+ @interpreter_context[:current_path] = path
119
124
  selections_by_name = {}
120
125
  gather_selections(owner_object, owner_type, selections, selections_by_name)
121
126
  selections_by_name.each do |result_name, field_ast_nodes_or_ast_node|
@@ -158,6 +163,7 @@ module GraphQL
158
163
  @interpreter_context[:current_path] = next_path
159
164
  @interpreter_context[:current_field] = field_defn
160
165
 
166
+ context.scoped_context = scoped_context
161
167
  object = owner_object
162
168
 
163
169
  if is_introspection
@@ -195,6 +201,8 @@ module GraphQL
195
201
  end
196
202
  end
197
203
 
204
+ @interpreter_context[:current_arguments] = kwarg_arguments
205
+
198
206
  # Optimize for the case that field is selected only once
199
207
  if field_ast_nodes.nil? || field_ast_nodes.size == 1
200
208
  next_selections = ast_node.selections
@@ -206,13 +214,15 @@ module GraphQL
206
214
  field_result = resolve_with_directives(object, ast_node) do
207
215
  # Actually call the field resolver and capture the result
208
216
  app_result = begin
209
- query.trace("execute_field", {owner: owner_type, field: field_defn, path: next_path, query: query, object: object, arguments: kwarg_arguments}) do
210
- field_defn.resolve(object, kwarg_arguments, context)
217
+ query.with_error_handling do
218
+ query.trace("execute_field", {owner: owner_type, field: field_defn, path: next_path, query: query, object: object, arguments: kwarg_arguments}) do
219
+ field_defn.resolve(object, kwarg_arguments, context)
220
+ end
211
221
  end
212
222
  rescue GraphQL::ExecutionError => err
213
223
  err
214
224
  end
215
- after_lazy(app_result, owner: owner_type, field: field_defn, path: next_path, owner_object: object, arguments: kwarg_arguments) do |inner_result|
225
+ 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|
216
226
  continue_value = continue_value(next_path, inner_result, field_defn, return_type.non_null?, ast_node)
217
227
  if HALT != continue_value
218
228
  continue_field(next_path, continue_value, field_defn, return_type, ast_node, next_selections, false, object, kwarg_arguments)
@@ -286,7 +296,7 @@ module GraphQL
286
296
  r
287
297
  when "UNION", "INTERFACE"
288
298
  resolved_type_or_lazy = query.resolve_type(type, value)
289
- after_lazy(resolved_type_or_lazy, owner: type, path: path, field: field, owner_object: owner_object, arguments: arguments) do |resolved_type|
299
+ 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|
290
300
  possible_types = query.possible_types(type)
291
301
 
292
302
  if !possible_types.include?(resolved_type)
@@ -306,12 +316,12 @@ module GraphQL
306
316
  rescue GraphQL::ExecutionError => err
307
317
  err
308
318
  end
309
- after_lazy(object_proxy, owner: type, path: path, field: field, owner_object: owner_object, arguments: arguments) do |inner_object|
319
+ after_lazy(object_proxy, owner: type, path: path, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments) do |inner_object|
310
320
  continue_value = continue_value(path, inner_object, field, is_non_null, ast_node)
311
321
  if HALT != continue_value
312
322
  response_hash = {}
313
323
  write_in_response(path, response_hash)
314
- evaluate_selections(path, continue_value, type, next_selections)
324
+ evaluate_selections(path, context.scoped_context, continue_value, type, next_selections)
315
325
  response_hash
316
326
  end
317
327
  end
@@ -320,6 +330,7 @@ module GraphQL
320
330
  write_in_response(path, response_list)
321
331
  inner_type = type.of_type
322
332
  idx = 0
333
+ scoped_context = context.scoped_context
323
334
  value.each do |inner_value|
324
335
  next_path = path.dup
325
336
  next_path << idx
@@ -327,7 +338,7 @@ module GraphQL
327
338
  idx += 1
328
339
  set_type_at_path(next_path, inner_type)
329
340
  # This will update `response_list` with the lazy
330
- after_lazy(inner_value, owner: inner_type, path: next_path, field: field, owner_object: owner_object, arguments: arguments) do |inner_inner_value|
341
+ 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|
331
342
  # reset `is_non_null` here and below, because the inner type will have its own nullability constraint
332
343
  continue_value = continue_value(next_path, inner_inner_value, field, false, ast_node)
333
344
  if HALT != continue_value
@@ -393,23 +404,30 @@ module GraphQL
393
404
  # @param field [GraphQL::Schema::Field]
394
405
  # @param eager [Boolean] Set to `true` for mutation root fields only
395
406
  # @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.
396
- def after_lazy(lazy_obj, owner:, field:, path:, owner_object:, arguments:, eager: false)
407
+ def after_lazy(lazy_obj, owner:, field:, path:, scoped_context:, owner_object:, arguments:, eager: false)
408
+ @interpreter_context[:current_object] = owner_object
409
+ @interpreter_context[:current_arguments] = arguments
397
410
  @interpreter_context[:current_path] = path
398
411
  @interpreter_context[:current_field] = field
399
412
  if schema.lazy?(lazy_obj)
400
413
  lazy = GraphQL::Execution::Lazy.new(path: path, field: field) do
401
414
  @interpreter_context[:current_path] = path
402
415
  @interpreter_context[:current_field] = field
416
+ @interpreter_context[:current_object] = owner_object
417
+ @interpreter_context[:current_arguments] = arguments
418
+ context.scoped_context = scoped_context
403
419
  # Wrap the execution of _this_ method with tracing,
404
420
  # but don't wrap the continuation below
405
421
  inner_obj = begin
406
- query.trace("execute_field_lazy", {owner: owner, field: field, path: path, query: query, object: owner_object, arguments: arguments}) do
407
- schema.sync_lazy(lazy_obj)
422
+ query.with_error_handling do
423
+ query.trace("execute_field_lazy", {owner: owner, field: field, path: path, query: query, object: owner_object, arguments: arguments}) do
424
+ schema.sync_lazy(lazy_obj)
425
+ end
408
426
  end
409
427
  rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => err
410
428
  yield(err)
411
429
  end
412
- after_lazy(inner_obj, owner: owner, field: field, path: path, owner_object: owner_object, arguments: arguments, eager: eager) do |really_inner_obj|
430
+ 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|
413
431
  yield(really_inner_obj)
414
432
  end
415
433
  end
@@ -500,7 +518,11 @@ module GraphQL
500
518
  args = arguments(nil, arg_type, ast_value)
501
519
  # We're not tracking defaults_used, but for our purposes
502
520
  # we compare the value to the default value.
503
- return true, arg_type.new(ruby_kwargs: args, context: context, defaults_used: nil)
521
+
522
+ input_obj = query.with_error_handling do
523
+ arg_type.new(ruby_kwargs: args, context: context, defaults_used: nil)
524
+ end
525
+ return true, input_obj
504
526
  else
505
527
  flat_value = flatten_ast_value(ast_value)
506
528
  return true, arg_type.coerce_input(flat_value, context)
@@ -44,9 +44,9 @@ module GraphQL
44
44
  end
45
45
 
46
46
  class << self
47
- def run_all(schema, query_options, *args)
48
- queries = query_options.map { |opts| GraphQL::Query.new(schema, nil, opts) }
49
- run_queries(schema, queries, *args)
47
+ def run_all(schema, query_options, **kwargs)
48
+ queries = query_options.map { |opts| GraphQL::Query.new(schema, nil, **opts) }
49
+ run_queries(schema, queries, **kwargs)
50
50
  end
51
51
 
52
52
  # @param schema [GraphQL::Schema]
@@ -15,8 +15,9 @@ module GraphQL
15
15
  end
16
16
 
17
17
  def __type(name:)
18
- type = context.warden.get_type(name)
18
+ return unless context.warden.reachable_type?(name)
19
19
 
20
+ type = context.warden.get_type(name)
20
21
  if type && context.interpreter?
21
22
  type = type.metadata[:type_class] || raise("Invariant: interpreter requires class-based type for #{name}")
22
23
  end
@@ -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
- types = @context.warden.types
18
+ types = @context.warden.reachable_types.sort_by(&:graphql_name)
18
19
  if context.interpreter?
19
20
  types.map { |t| t.metadata[:type_class] || raise("Invariant: can't introspect non-class-based type: #{t}") }
20
21
  else
@@ -23,10 +23,16 @@ module GraphQL
23
23
  @include_built_in_scalars = include_built_in_scalars
24
24
  @include_built_in_directives = include_built_in_directives
25
25
 
26
+ filter = GraphQL::Filter.new(only: only, except: except)
27
+ if @schema.respond_to?(:visible?)
28
+ filter = filter.merge(only: @schema.method(:visible?))
29
+ end
30
+
31
+ schema_context = schema.context_class.new(query: nil, object: nil, schema: schema, values: context)
26
32
  @warden = GraphQL::Schema::Warden.new(
27
- GraphQL::Filter.new(only: only, except: except),
33
+ filter,
28
34
  schema: @schema,
29
- context: context,
35
+ context: schema_context,
30
36
  )
31
37
  end
32
38
 
@@ -250,7 +256,7 @@ module GraphQL
250
256
  definitions = []
251
257
  definitions << build_schema_node if include_schema_node?
252
258
  definitions += build_directive_nodes(warden.directives)
253
- definitions += build_type_definition_nodes(warden.types)
259
+ definitions += build_type_definition_nodes(warden.reachable_types)
254
260
  definitions
255
261
  end
256
262
 
@@ -25,7 +25,7 @@ module GraphQL
25
25
  # Initialize a node by extracting its position,
26
26
  # then calling the class's `initialize_node` method.
27
27
  # @param options [Hash] Initial attributes for this node
28
- def initialize(options={})
28
+ def initialize(options = {})
29
29
  if options.key?(:position_source)
30
30
  position_source = options.delete(:position_source)
31
31
  @line = position_source.line
@@ -34,7 +34,7 @@ module GraphQL
34
34
 
35
35
  @filename = options.delete(:filename)
36
36
 
37
- initialize_node(options)
37
+ initialize_node(**options)
38
38
  end
39
39
 
40
40
  # Value equality
@@ -134,7 +134,6 @@ module GraphQL
134
134
  @executed = false
135
135
 
136
136
  # TODO add a general way to define schema-level filters
137
- # TODO also add this to schema dumps
138
137
  if @schema.respond_to?(:visible?)
139
138
  merge_filters(only: @schema.method(:visible?))
140
139
  end
@@ -300,6 +299,13 @@ module GraphQL
300
299
  with_prepared_ast { @subscription }
301
300
  end
302
301
 
302
+ # @api private
303
+ def with_error_handling
304
+ schema.error_handler.with_error_handling(context) do
305
+ yield
306
+ end
307
+ end
308
+
303
309
  private
304
310
 
305
311
  def find_operation(operations, operation_name)