graphql 2.6.1 → 2.6.2

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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql/dataloader.rb +1 -1
  3. data/lib/graphql/execution/field_resolve_step.rb +165 -65
  4. data/lib/graphql/execution/finalize.rb +18 -7
  5. data/lib/graphql/execution/input_values.rb +110 -38
  6. data/lib/graphql/execution/interpreter/runtime.rb +36 -15
  7. data/lib/graphql/execution/load_argument_step.rb +35 -3
  8. data/lib/graphql/execution/next.rb +20 -12
  9. data/lib/graphql/execution/prepare_object_step.rb +18 -5
  10. data/lib/graphql/execution/resolve_type_step.rb +27 -0
  11. data/lib/graphql/execution/runner.rb +64 -29
  12. data/lib/graphql/execution/selections_step.rb +1 -1
  13. data/lib/graphql/execution.rb +8 -1
  14. data/lib/graphql/execution_error.rb +6 -12
  15. data/lib/graphql/introspection/entry_points.rb +2 -2
  16. data/lib/graphql/introspection/schema_type.rb +6 -2
  17. data/lib/graphql/language/lexer.rb +1 -1
  18. data/lib/graphql/language/parser.rb +1 -1
  19. data/lib/graphql/pagination/connections.rb +1 -3
  20. data/lib/graphql/query.rb +2 -2
  21. data/lib/graphql/schema/argument.rb +2 -2
  22. data/lib/graphql/schema/directive/feature.rb +4 -0
  23. data/lib/graphql/schema/directive/transform.rb +20 -0
  24. data/lib/graphql/schema/has_single_input_argument.rb +24 -13
  25. data/lib/graphql/schema/input_object.rb +4 -0
  26. data/lib/graphql/schema/ractor_shareable.rb +1 -0
  27. data/lib/graphql/schema/relay_classic_mutation.rb +16 -2
  28. data/lib/graphql/schema/resolver.rb +0 -7
  29. data/lib/graphql/schema/subscription.rb +53 -8
  30. data/lib/graphql/schema/timeout.rb +2 -2
  31. data/lib/graphql/schema/visibility/visit.rb +1 -1
  32. data/lib/graphql/schema.rb +30 -9
  33. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +6 -0
  34. data/lib/graphql/subscriptions/event.rb +0 -1
  35. data/lib/graphql/tracing/perfetto_trace.rb +5 -3
  36. data/lib/graphql/version.rb +1 -1
  37. metadata +3 -2
@@ -2,7 +2,7 @@
2
2
  module GraphQL
3
3
  module Execution
4
4
  class Runner
5
- def initialize(multiplex, authorization:)
5
+ def initialize(multiplex)
6
6
  @multiplex = multiplex
7
7
  @schema = multiplex.schema
8
8
  @steps_queue = []
@@ -32,23 +32,13 @@ module GraphQL
32
32
  end
33
33
 
34
34
  @lazy_cache = resolves_lazies ? {}.compare_by_identity : nil
35
- @authorization = authorization
36
- if @authorization
37
- @authorizes_cache = Hash.new do |h, query_context|
38
- h[query_context] = {}.compare_by_identity
39
- end.compare_by_identity
40
- end
35
+ @authorizes_cache = Hash.new do |h, query_context|
36
+ h[query_context] = {}.compare_by_identity
37
+ end.compare_by_identity
41
38
  end
42
39
 
43
40
  attr_reader :runtime_directives, :uses_runtime_directives, :finalizer_keys
44
41
 
45
- def resolve_type(type, object, query)
46
- query.current_trace.begin_resolve_type(type, object, query.context)
47
- resolved_type, _ignored_new_value = query.resolve_type(type, object)
48
- query.current_trace.end_resolve_type(type, object, query.context, resolved_type)
49
- resolved_type
50
- end
51
-
52
42
  def authorizes?(graphql_definition, query_context)
53
43
  auth_cache = @authorizes_cache[query_context]
54
44
  case (auth_res = auth_cache[graphql_definition])
@@ -63,7 +53,7 @@ module GraphQL
63
53
  @dataloader.append_job(step)
64
54
  end
65
55
 
66
- attr_reader :authorization, :steps_queue, :schema, :variables, :dataloader, :resolves_lazies, :authorizes, :static_type_at, :runtime_type_at, :finalizers, :input_values
56
+ attr_reader :steps_queue, :schema, :variables, :dataloader, :resolves_lazies, :authorizes, :static_type_at, :runtime_type_at, :finalizers, :input_values
67
57
 
68
58
  # @return [void]
69
59
  def add_finalizer(query, result_value, key, finalizer)
@@ -120,6 +110,10 @@ module GraphQL
120
110
  trace.execute_query(query: query) do
121
111
  begin_execute(isolated_steps, results, query, root_type, root_value)
122
112
  end
113
+ rescue GraphQL::RuntimeError => err
114
+ err.ast_node = query.selected_operation
115
+ err.path = query.path
116
+ query.context.add_error(err)
123
117
  end
124
118
 
125
119
  trace.execute_query_lazy(query: nil, multiplex: @multiplex) do
@@ -137,8 +131,10 @@ module GraphQL
137
131
  fin_result = if (!@finalizers&.key?(query) && query.context.errors.empty?) || !query.valid?
138
132
  result
139
133
  else
140
- data = result["data"]
141
- data = Finalize.new(query, data, self).run
134
+ if result
135
+ data = result["data"]
136
+ data = Finalize.new(query, data, self).run
137
+ end
142
138
  errors = []
143
139
  query.context.errors.each do |err|
144
140
  if err.respond_to?(:to_h)
@@ -154,6 +150,9 @@ module GraphQL
154
150
  end
155
151
 
156
152
  query.result_values = fin_result
153
+ if query.context.namespace?(:__query_result_extensions__)
154
+ query.result_values["extensions"] = query.context.namespace(:__query_result_extensions__)
155
+ end
157
156
  query.result
158
157
  end
159
158
  end
@@ -228,12 +227,13 @@ module GraphQL
228
227
 
229
228
  def begin_execute(isolated_steps, results, query, root_type, root_value)
230
229
  data = {}
230
+ @static_type_at[data] = root_type
231
231
  selected_operation = query.selected_operation
232
232
  beginning_path = query.path
233
233
 
234
234
  case root_type.kind.name
235
235
  when "OBJECT"
236
- if self.authorization && authorizes?(root_type, query.context)
236
+ if authorizes?(root_type, query.context)
237
237
  query.current_trace.begin_authorized(root_type, root_value, query.context)
238
238
  auth_check = schema.sync_lazy(root_type.authorized?(root_value, query.context))
239
239
  query.current_trace.end_authorized(root_type, root_value, query.context, auth_check)
@@ -265,10 +265,40 @@ module GraphQL
265
265
  objects = [root_value]
266
266
  query.current_trace.objects(root_type, objects, query.context)
267
267
 
268
+ if query.is_a?(GraphQL::Query) && uses_runtime_directives && (query_dirs = selected_operation.directives).any? # rubocop:disable Development/NoneWithoutBlockCop
269
+ continue_execution = true
270
+ query_dirs.each do |dir_node|
271
+ dir_defn = runtime_directives[dir_node.name] || raise(GraphQL::Error, "No directive definition found for: #{dir_node.name.inspect}")
272
+ dir_args, errors = input_values[query].argument_values(dir_defn, dir_node.arguments, nil) # rubocop:disable Development/ContextIsPassedCop
273
+ if errors
274
+ errors.each { |e|
275
+ e.ast_node = dir_node
276
+ e.path = beginning_path
277
+ query.context.add_error(e)
278
+ }
279
+ continue_execution = false
280
+ break
281
+ end
282
+ result = dir_defn.resolve_operation(selected_operation, query, objects, dir_args, query.context)
283
+ if result.is_a?(Finalizer)
284
+ result.path = path
285
+ add_finalizer(query, result, nil, data)
286
+ if result.is_a?(HaltExecution)
287
+ continue_execution = false
288
+ break
289
+ end
290
+ end
291
+ end
292
+
293
+ if !continue_execution
294
+ return
295
+ end
296
+ end
297
+
268
298
  if query.query?
269
299
  isolated_steps[0] << SelectionsStep.new(
270
300
  parent_type: root_type,
271
- selections: query.selected_operation.selections,
301
+ selections: selected_operation.selections,
272
302
  objects: objects,
273
303
  results: [data],
274
304
  path: beginning_path,
@@ -313,17 +343,19 @@ module GraphQL
313
343
  raise ArgumentError, "Unknown operation type (not query, mutation or subscription): #{query.query_string}"
314
344
  end
315
345
  when "UNION", "INTERFACE"
316
- resolved_type = resolve_type(root_type, root_value, query)
317
- if resolves_lazies
346
+ resolved_type = ResolveTypeStep.resolve_type(root_type, root_value, query)
347
+ if resolves_lazies && lazy?(resolved_type)
318
348
  resolved_type = schema.sync_lazy(resolved_type)
319
349
  end
350
+ resolved_type, root_value = resolved_type
351
+ ResolveTypeStep.assert_valid_resolved_type(root_type, resolved_type, root_value, nil, query: query)
320
352
  objects = [root_value]
321
353
  query.current_trace.objects(resolved_type, objects, query.context)
322
354
  runtime_type_at[data] = resolved_type
323
355
  results << { "data" => data }
324
356
  isolated_steps[0] << SelectionsStep.new(
325
357
  parent_type: resolved_type,
326
- selections: query.selected_operation.selections,
358
+ selections: selected_operation.selections,
327
359
  objects: objects,
328
360
  results: [data],
329
361
  path: beginning_path,
@@ -340,7 +372,7 @@ module GraphQL
340
372
  results << { "data" => list_result }
341
373
  isolated_steps[0] << SelectionsStep.new(
342
374
  parent_type: inner_type,
343
- selections: query.selected_operation.selections,
375
+ selections: selected_operation.selections,
344
376
  objects: root_value,
345
377
  results: list_result,
346
378
  path: beginning_path,
@@ -353,18 +385,21 @@ module GraphQL
353
385
  else
354
386
  raise "Unhandled root type kind: #{root_type.kind.name.inspect}"
355
387
  end
356
-
357
- @static_type_at[data] = root_type
358
388
  end
359
389
 
360
390
  def directives_include?(query, ast_selection)
361
391
  if ast_selection.directives.any? { |dir_node|
362
- if dir_node.name == "skip"
363
- skip_args = @input_values[query].argument_values(GraphQL::Schema::Directive::Skip, dir_node.arguments, nil) # rubocop:disable Development/ContextIsPassedCop
392
+ case dir_node.name
393
+ when "skip"
394
+ skip_args, _errors = @input_values[query].argument_values(GraphQL::Schema::Directive::Skip, dir_node.arguments, nil) # rubocop:disable Development/ContextIsPassedCop
364
395
  skip_args[:if] == true
365
- elsif dir_node.name == "include"
366
- include_args = @input_values[query].argument_values(GraphQL::Schema::Directive::Include, dir_node.arguments, nil) # rubocop:disable Development/ContextIsPassedCop
396
+ when "include"
397
+ include_args, _errors = @input_values[query].argument_values(GraphQL::Schema::Directive::Include, dir_node.arguments, nil) # rubocop:disable Development/ContextIsPassedCop
367
398
  include_args[:if] == false
399
+ else
400
+ dir_defn = runtime_directives[dir_node.name]
401
+ dir_args, _errors = @input_values[query].argument_values(dir_defn, dir_node.arguments, nil) # rubocop:disable Development/ContextIsPassedCop
402
+ !dir_defn.include?(nil, dir_args, query.context)
368
403
  end
369
404
  }
370
405
  false
@@ -38,7 +38,7 @@ module GraphQL
38
38
  directives.each do |dir_node|
39
39
  dir_defn = @runner.runtime_directives[dir_node.name]
40
40
  if dir_defn # not present for `skip` or `include`
41
- dir_args = @runner.input_values[query].argument_values(dir_defn, dir_node.arguments, nil) # rubocop:disable Development/ContextIsPassedCop
41
+ dir_args, _errors = @runner.input_values[query].argument_values(dir_defn, dir_node.arguments, nil) # rubocop:disable Development/ContextIsPassedCop
42
42
  result = case directives_owner
43
43
  when Language::Nodes::FragmentSpread
44
44
  dir_defn.resolve_fragment_spread(directives_owner, @parent_type, @objects, dir_args, self.query.context)
@@ -15,7 +15,14 @@ module GraphQL
15
15
  def ast_nodes=(_ignored); end
16
16
 
17
17
  def finalize_graphql_result(query, result_data, key)
18
- result_data.delete(key)
18
+ case result_data
19
+ when Hash
20
+ result_data.delete(key)
21
+ when Array
22
+ result_data.delete_at(key)
23
+ else
24
+ raise "Unexpected result data #{result_data.class}: #{result_data}"
25
+ end
19
26
  end
20
27
  end
21
28
  end
@@ -4,17 +4,6 @@ module GraphQL
4
4
  # the error will be inserted into the response's `"errors"` key
5
5
  # and the field will resolve to `nil`.
6
6
  class ExecutionError < GraphQL::RuntimeError
7
- # @return [GraphQL::Language::Nodes::Field] the field where the error occurred
8
- def ast_node
9
- ast_nodes&.first
10
- end
11
-
12
- def ast_node=(new_node)
13
- @ast_nodes = [new_node]
14
- end
15
-
16
- attr_accessor :ast_nodes
17
-
18
7
  # @return [String] an array describing the JSON-path into the execution
19
8
  # response which corresponds to this error.
20
9
  attr_accessor :path
@@ -37,7 +26,12 @@ module GraphQL
37
26
  end
38
27
 
39
28
  def finalize_graphql_result(query, result_data, key)
40
- result_data[key] = nil
29
+ if ast_node.is_a?(GraphQL::Language::Nodes::Directive)
30
+ # This is for backwards compatibility ... what does the spec say?
31
+ result_data.delete(key)
32
+ else
33
+ result_data[key] = nil
34
+ end
41
35
  end
42
36
 
43
37
  # @return [Hash] An entry for the response's "errors" key
@@ -2,8 +2,8 @@
2
2
  module GraphQL
3
3
  module Introspection
4
4
  class EntryPoints < Introspection::BaseObject
5
- field :__schema, GraphQL::Schema::LateBoundType.new("__Schema"), "This GraphQL schema", null: false, dynamic_introspection: true, resolve_static: :__schema
6
- field :__type, GraphQL::Schema::LateBoundType.new("__Type"), "A type in the GraphQL system", dynamic_introspection: true, resolve_static: :__type do
5
+ field :__schema, GraphQL::Schema::LateBoundType.new("__Schema"), "This GraphQL schema", null: false, dynamic_introspection: true, resolve_static: true
6
+ field :__type, GraphQL::Schema::LateBoundType.new("__Type"), "A type in the GraphQL system", dynamic_introspection: true, resolve_static: true do
7
7
  argument :name, String
8
8
  end
9
9
 
@@ -13,12 +13,16 @@ module GraphQL
13
13
  field :mutation_type, GraphQL::Schema::LateBoundType.new("__Type"), "If this server supports mutation, the type that mutation operations will be rooted at."
14
14
  field :subscription_type, GraphQL::Schema::LateBoundType.new("__Type"), "If this server support subscription, the type that subscription operations will be rooted at."
15
15
  field :directives, [GraphQL::Schema::LateBoundType.new("__Directive")], "A list of all directives supported by this server.", null: false, scope: false
16
- field :description, String, resolver_method: :schema_description
16
+ field :description, String, resolver_method: :schema_description, resolve_static: :schema_description
17
17
 
18
- def schema_description
18
+ def self.schema_description(context)
19
19
  context.schema.description
20
20
  end
21
21
 
22
+ def schema_description
23
+ self.class.schema_description(context)
24
+ end
25
+
22
26
  def types
23
27
  query_types = context.types.all_types
24
28
  types = query_types + context.schema.extra_types
@@ -130,7 +130,7 @@ module GraphQL
130
130
  elsif token_name == :STRING
131
131
  string_value
132
132
  elsif @scanner.matched_size.nil?
133
- @scanner.peek(1)
133
+ @string.byteslice(@scanner.pos - 1, 1)
134
134
  else
135
135
  token_value
136
136
  end
@@ -806,7 +806,7 @@ module GraphQL
806
806
 
807
807
  def expect_token(expected_token_name)
808
808
  unless @token_name == expected_token_name
809
- raise_parse_error("Expected #{expected_token_name}, actual: #{token_name || "(none)"} (#{debug_token_value.inspect})")
809
+ raise_parse_error("Expected #{expected_token_name}, #{@token_name == false ? "not end of file" : "actual: #{@token_name} (#{debug_token_value.inspect})"}")
810
810
  end
811
811
  advance_token
812
812
  end
@@ -85,9 +85,7 @@ module GraphQL
85
85
 
86
86
  def populate_connection(field, object, value, original_arguments, context)
87
87
  if value.is_a? GraphQL::ExecutionError
88
- # This isn't even going to work because context doesn't have ast_node anymore
89
- context.add_error(value)
90
- nil
88
+ raise value
91
89
  elsif value.nil?
92
90
  nil
93
91
  elsif value.is_a?(GraphQL::Pagination::Connection)
data/lib/graphql/query.rb CHANGED
@@ -45,8 +45,8 @@ module GraphQL
45
45
  end
46
46
 
47
47
  # @api private
48
- def handle_or_reraise(err)
49
- @schema.handle_or_reraise(context, err)
48
+ def handle_or_reraise(err, **kwargs)
49
+ @schema.handle_or_reraise(context, err, **kwargs)
50
50
  end
51
51
  end
52
52
 
@@ -165,8 +165,8 @@ module GraphQL
165
165
  true
166
166
  end
167
167
 
168
- def authorizes?(_context)
169
- self.method(:authorized?).owner != GraphQL::Schema::Argument
168
+ def authorizes?(context)
169
+ self.method(:authorized?).owner != GraphQL::Schema::Argument || type.unwrap.authorizes?(context)
170
170
  end
171
171
 
172
172
  def authorized?(obj, value, ctx)
@@ -60,6 +60,10 @@ module GraphQL
60
60
  def self.enabled?(flag_name, object, context)
61
61
  raise GraphQL::RequiredImplementationMissingError, "Implement `.enabled?(flag_name, object, context)` to return true or false for the feature flag (#{flag_name.inspect})"
62
62
  end
63
+
64
+ def self.resolve_field(...); end
65
+ def self.resolve_fragment_spread(...); end
66
+ def self.resolve_inline_fragment(...); end
63
67
  end
64
68
  end
65
69
  end
@@ -54,6 +54,26 @@ module GraphQL
54
54
  nil
55
55
  end
56
56
  end
57
+
58
+ def self.resolve_field(ast_nodes, parent_type, field_defn, objects, arguments, context)
59
+ transform_name = arguments[:by]
60
+ if TRANSFORMS.include?(transform_name)
61
+ Transformer.new(transform_name)
62
+ else
63
+ nil
64
+ end
65
+ end
66
+
67
+ class Transformer
68
+ include Execution::PostProcessor
69
+ def initialize(transform)
70
+ @transform = transform
71
+ end
72
+ def after_resolve(field_results)
73
+ field_results.map! { |r| r.respond_to?(@transform) ? r.public_send(@transform) : r }
74
+ field_results
75
+ end
76
+ end
57
77
  end
58
78
  end
59
79
  end
@@ -4,10 +4,30 @@ module GraphQL
4
4
  class Schema
5
5
  module HasSingleInputArgument
6
6
  def resolve_with_support(**inputs)
7
- if inputs[:input].is_a?(InputObject)
8
- input = inputs[:input].to_kwargs
7
+ input_kwargs = flatten_arguments(inputs)
8
+ if !input_kwargs.empty?
9
+ super(**input_kwargs)
10
+ else
11
+ super()
12
+ end
13
+ end
14
+
15
+ def self.included(base)
16
+ base.extend(ClassMethods)
17
+ end
18
+
19
+ def call
20
+ @prepared_arguments = flatten_arguments(@prepared_arguments)
21
+ super
22
+ end
23
+
24
+ private
25
+
26
+ def flatten_arguments(inputs)
27
+ input = if inputs[:input].is_a?(InputObject)
28
+ inputs[:input].to_kwargs
9
29
  else
10
- input = inputs[:input]
30
+ inputs[:input]
11
31
  end
12
32
 
13
33
  new_extras = field ? field.extras : []
@@ -24,7 +44,6 @@ module GraphQL
24
44
  end
25
45
 
26
46
  if input
27
- # This is handled by Relay::Mutation::Resolve, a bit hacky, but here we are.
28
47
  input_kwargs = input.to_h
29
48
  else
30
49
  # Relay Classic Mutations with no `argument`s
@@ -32,15 +51,7 @@ module GraphQL
32
51
  input_kwargs = {}
33
52
  end
34
53
 
35
- if !input_kwargs.empty?
36
- super(**input_kwargs)
37
- else
38
- super()
39
- end
40
- end
41
-
42
- def self.included(base)
43
- base.extend(ClassMethods)
54
+ input_kwargs
44
55
  end
45
56
 
46
57
  module ClassMethods
@@ -113,6 +113,10 @@ module GraphQL
113
113
  end
114
114
 
115
115
  class << self
116
+ def authorizes?(ctx)
117
+ self.method(:authorized?).owner != GraphQL::Schema::InputObject
118
+ end
119
+
116
120
  def authorized?(obj, value, ctx)
117
121
  # Authorize each argument (but this doesn't apply if `prepare` is implemented):
118
122
  if value.respond_to?(:key?)
@@ -72,6 +72,7 @@ module GraphQL
72
72
  # How to support it?
73
73
  def lazy?(_obj); false; end
74
74
  def sync_lazy(obj); obj; end
75
+ def resolves_lazies?; false; end
75
76
  end
76
77
  end
77
78
  end
@@ -25,7 +25,7 @@ module GraphQL
25
25
  argument :client_mutation_id, String, "A unique identifier for the client performing the mutation.", required: false
26
26
 
27
27
  # The payload should always include this field
28
- field(:client_mutation_id, String, "A unique identifier for the client performing the mutation.")
28
+ field(:client_mutation_id, String, "A unique identifier for the client performing the mutation.", hash_key: :client_mutation_id)
29
29
  # Relay classic default:
30
30
  null(true)
31
31
 
@@ -35,7 +35,6 @@ module GraphQL
35
35
  input = inputs[:input].to_kwargs
36
36
 
37
37
  if input
38
- # This is handled by Relay::Mutation::Resolve, a bit hacky, but here we are.
39
38
  input_kwargs = input.to_h
40
39
  client_mutation_id = input_kwargs.delete(:client_mutation_id)
41
40
  inputs[:input] = input_kwargs
@@ -51,6 +50,21 @@ module GraphQL
51
50
  return_hash
52
51
  end
53
52
  end
53
+
54
+ def call
55
+ input = @prepared_arguments[:input]&.to_kwargs
56
+
57
+ if input
58
+ client_mutation_id = input.delete(:client_mutation_id)
59
+ @prepared_arguments[:input] = input
60
+ end
61
+
62
+ super
63
+
64
+ if (return_value = exec_result[exec_index]).is_a?(Hash)
65
+ return_value[:client_mutation_id] = client_mutation_id
66
+ end
67
+ end
54
68
  end
55
69
  end
56
70
  end
@@ -60,9 +60,6 @@ module GraphQL
60
60
  attr_writer :prepared_arguments
61
61
 
62
62
  def call
63
- if self.class < Schema::HasSingleInputArgument
64
- @prepared_arguments = @prepared_arguments[:input]
65
- end
66
63
  q = context.query
67
64
  trace_objs = [object]
68
65
  q.current_trace.begin_execute_field(field, @prepared_arguments, trace_objs, q)
@@ -96,10 +93,6 @@ module GraphQL
96
93
 
97
94
  result = if is_authed
98
95
  Schema::Validator.validate!(self.class.validators, object, context, @prepared_arguments, as: @field)
99
- if q.subscription? && @field.owner == context.schema.subscription
100
- # This needs to use arguments without `loads:`. TODO extract this into subscription-related code somehow?
101
- @original_arguments = @field_resolve_step.runner.input_values[q].argument_values(@field, @field_resolve_step.ast_node.arguments, nil)
102
- end
103
96
  call_resolve(@prepared_arguments)
104
97
  elsif new_return_value.nil?
105
98
  err = UnauthorizedFieldError.new(object: object, type: @field_resolve_step.parent_type, context: context, field: @field)
@@ -30,14 +30,47 @@ module GraphQL
30
30
  end
31
31
  end
32
32
 
33
+ # @api private
34
+ def call_resolve(args_hash)
35
+ if @field_resolve_step.nil?
36
+ super
37
+ else
38
+ context.namespace(:subscriptions)[:update_event] = event
39
+ result = nil
40
+ unsubscribed = true
41
+ unsubscribed_result = nil
42
+ begin
43
+ result = super
44
+ unsubscribed = false
45
+ rescue EarlyUnsubscribe => err
46
+ unsubscribed_result = err.unsubscribed_result
47
+ end
48
+
49
+
50
+ if unsubscribed
51
+ if unsubscribed_result
52
+ context.namespace(:subscriptions)[:final_update] = true
53
+ unsubscribed_result
54
+ else
55
+ context.skip
56
+ end
57
+ else
58
+ result
59
+ end
60
+ end
61
+ end
62
+
33
63
  # @api private
34
64
  def resolve_with_support(**args)
35
65
  @original_arguments = args # before `loads:` have been run
36
66
  result = nil
37
67
  unsubscribed = true
38
- unsubscribed_result = catch :graphql_subscription_unsubscribed do
68
+ unsubscribed_result = nil
69
+ begin
39
70
  result = super
40
71
  unsubscribed = false
72
+ rescue EarlyUnsubscribe => err
73
+ unsubscribed_result = err.unsubscribed_result
41
74
  end
42
75
 
43
76
 
@@ -114,7 +147,13 @@ module GraphQL
114
147
  # @return [void]
115
148
  def unsubscribe(update_value = nil)
116
149
  context.namespace(:subscriptions)[:unsubscribed] = true
117
- throw :graphql_subscription_unsubscribed, update_value
150
+ err = EarlyUnsubscribe.new
151
+ err.unsubscribed_result = update_value
152
+ raise err
153
+ end
154
+
155
+ class EarlyUnsubscribe < GraphQL::RuntimeError
156
+ attr_accessor :unsubscribed_result
118
157
  end
119
158
 
120
159
  # Call this method to provide a new subscription_scope; OR
@@ -187,12 +226,18 @@ module GraphQL
187
226
 
188
227
  # @return [Subscriptions::Event] This object is used as a representation of this subscription for the backend
189
228
  def event
190
- @event ||= Subscriptions::Event.new(
191
- name: field.name,
192
- arguments: @original_arguments,
193
- context: context,
194
- field: field,
195
- )
229
+ @event ||= begin
230
+ if @original_arguments.nil? && @field_resolve_step
231
+ @original_arguments, _errors = @field_resolve_step.arguments_without_loads
232
+ end
233
+
234
+ Subscriptions::Event.new(
235
+ name: field.name,
236
+ arguments: @original_arguments,
237
+ context: context,
238
+ field: field,
239
+ )
240
+ end
196
241
  end
197
242
  end
198
243
  end
@@ -68,7 +68,7 @@ module GraphQL
68
68
  super
69
69
  end
70
70
 
71
- def execute_field(query:, field:, **_rest)
71
+ def begin_execute_field(field, _arguments, _objects, query)
72
72
  timeout_state = query.context.namespace(@timeout).fetch(:state)
73
73
  # If the `:state` is `false`, then `max_seconds(query)` opted out of timeout for this query.
74
74
  if timeout_state == false
@@ -84,7 +84,7 @@ module GraphQL
84
84
 
85
85
  # `handle_timeout` may have set this to be `false`
86
86
  if timeout_state != false
87
- error
87
+ raise error
88
88
  else
89
89
  super
90
90
  end
@@ -102,7 +102,7 @@ module GraphQL
102
102
 
103
103
  missed_late_types_streak = 0
104
104
  while (owner, late_type = @late_bound_types.shift)
105
- if (late_type.is_a?(String) && (type = Member::BuildType.constantize(type))) ||
105
+ if (late_type.is_a?(String) && (type = Member::BuildType.constantize(late_type))) ||
106
106
  (late_type.is_a?(LateBoundType) && (type = @visited_types.find { |t| t.graphql_name == late_type.graphql_name }))
107
107
  missed_late_types_streak = 0 # might succeed next round
108
108
  update_type_owner(owner, type)