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.
- checksums.yaml +4 -4
- data/lib/graphql/dataloader.rb +1 -1
- data/lib/graphql/execution/field_resolve_step.rb +165 -65
- data/lib/graphql/execution/finalize.rb +18 -7
- data/lib/graphql/execution/input_values.rb +110 -38
- data/lib/graphql/execution/interpreter/runtime.rb +36 -15
- data/lib/graphql/execution/load_argument_step.rb +35 -3
- data/lib/graphql/execution/next.rb +20 -12
- data/lib/graphql/execution/prepare_object_step.rb +18 -5
- data/lib/graphql/execution/resolve_type_step.rb +27 -0
- data/lib/graphql/execution/runner.rb +64 -29
- data/lib/graphql/execution/selections_step.rb +1 -1
- data/lib/graphql/execution.rb +8 -1
- data/lib/graphql/execution_error.rb +6 -12
- data/lib/graphql/introspection/entry_points.rb +2 -2
- data/lib/graphql/introspection/schema_type.rb +6 -2
- data/lib/graphql/language/lexer.rb +1 -1
- data/lib/graphql/language/parser.rb +1 -1
- data/lib/graphql/pagination/connections.rb +1 -3
- data/lib/graphql/query.rb +2 -2
- data/lib/graphql/schema/argument.rb +2 -2
- data/lib/graphql/schema/directive/feature.rb +4 -0
- data/lib/graphql/schema/directive/transform.rb +20 -0
- data/lib/graphql/schema/has_single_input_argument.rb +24 -13
- data/lib/graphql/schema/input_object.rb +4 -0
- data/lib/graphql/schema/ractor_shareable.rb +1 -0
- data/lib/graphql/schema/relay_classic_mutation.rb +16 -2
- data/lib/graphql/schema/resolver.rb +0 -7
- data/lib/graphql/schema/subscription.rb +53 -8
- data/lib/graphql/schema/timeout.rb +2 -2
- data/lib/graphql/schema/visibility/visit.rb +1 -1
- data/lib/graphql/schema.rb +30 -9
- data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +6 -0
- data/lib/graphql/subscriptions/event.rb +0 -1
- data/lib/graphql/tracing/perfetto_trace.rb +5 -3
- data/lib/graphql/version.rb +1 -1
- metadata +3 -2
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
module GraphQL
|
|
3
3
|
module Execution
|
|
4
4
|
class Runner
|
|
5
|
-
def initialize(multiplex
|
|
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
|
-
@
|
|
36
|
-
|
|
37
|
-
|
|
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 :
|
|
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
|
-
|
|
141
|
-
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
363
|
-
|
|
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
|
-
|
|
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)
|
data/lib/graphql/execution.rb
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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:
|
|
6
|
-
field :__type, GraphQL::Schema::LateBoundType.new("__Type"), "A type in the GraphQL system", dynamic_introspection: true, resolve_static:
|
|
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
|
|
@@ -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},
|
|
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
|
-
|
|
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
|
@@ -165,8 +165,8 @@ module GraphQL
|
|
|
165
165
|
true
|
|
166
166
|
end
|
|
167
167
|
|
|
168
|
-
def authorizes?(
|
|
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
|
-
|
|
8
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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?)
|
|
@@ -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 =
|
|
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
|
-
|
|
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 ||=
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
|
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(
|
|
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)
|