graphql 1.9.16 → 1.9.21

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.
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)