graphql 2.6.1 → 2.6.3
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/analysis/query_complexity.rb +29 -13
- data/lib/graphql/backtrace/table.rb +10 -1
- data/lib/graphql/current.rb +7 -1
- data/lib/graphql/dataloader.rb +1 -1
- data/lib/graphql/execution/directive_checks.rb +2 -0
- data/lib/graphql/execution/field_resolve_step.rb +178 -65
- data/lib/graphql/execution/finalize.rb +21 -8
- data/lib/graphql/execution/input_values.rb +110 -38
- data/lib/graphql/execution/interpreter/arguments_cache.rb +3 -0
- data/lib/graphql/execution/interpreter/runtime.rb +36 -15
- data/lib/graphql/execution/load_argument_step.rb +41 -3
- data/lib/graphql/execution/next.rb +20 -12
- data/lib/graphql/execution/prepare_object_step.rb +24 -5
- data/lib/graphql/execution/resolve_type_step.rb +27 -0
- data/lib/graphql/execution/runner.rb +65 -30
- 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/language.rb +8 -2
- data/lib/graphql/pagination/connections.rb +1 -3
- data/lib/graphql/query.rb +2 -2
- data/lib/graphql/schema/argument.rb +3 -3
- 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/interface.rb +1 -1
- data/lib/graphql/schema/introspection_system.rb +6 -21
- data/lib/graphql/schema/printer.rb +1 -1
- 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/validator/allow_blank_validator.rb +3 -3
- data/lib/graphql/schema/validator/allow_null_validator.rb +3 -3
- data/lib/graphql/schema/validator/exclusion_validator.rb +2 -2
- data/lib/graphql/schema/validator/format_validator.rb +3 -3
- data/lib/graphql/schema/validator/inclusion_validator.rb +2 -2
- data/lib/graphql/schema/validator/length_validator.rb +6 -6
- data/lib/graphql/schema/validator/numericality_validator.rb +19 -19
- data/lib/graphql/schema/validator/required_validator.rb +6 -4
- data/lib/graphql/schema/validator.rb +9 -0
- data/lib/graphql/schema/visibility/profile.rb +6 -4
- data/lib/graphql/schema/visibility/visit.rb +1 -1
- data/lib/graphql/schema/visibility.rb +30 -22
- data/lib/graphql/schema.rb +31 -10
- 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,9 +110,13 @@ 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
|
-
trace.execute_query_lazy(query: nil, multiplex: @multiplex) do
|
|
119
|
+
trace.execute_query_lazy(query: @multiplex.queries.size == 1 ? @multiplex.queries.first : nil, multiplex: @multiplex) do
|
|
126
120
|
while (next_isolated_steps = isolated_steps.shift)
|
|
127
121
|
next_isolated_steps.each do |step|
|
|
128
122
|
add_step(step)
|
|
@@ -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
|
data/lib/graphql/language.rb
CHANGED
|
@@ -47,13 +47,19 @@ module GraphQL
|
|
|
47
47
|
def self.escape_single_quoted_newlines(query_str)
|
|
48
48
|
scanner = StringScanner.new(query_str)
|
|
49
49
|
inside_single_quoted_string = false
|
|
50
|
+
inside_triple_quoted_string = false
|
|
50
51
|
new_query_str = nil
|
|
51
52
|
while !scanner.eos?
|
|
52
|
-
if scanner.skip(
|
|
53
|
+
if scanner.skip('"""')
|
|
54
|
+
inside_triple_quoted_string = !inside_triple_quoted_string
|
|
55
|
+
new_query_str && (new_query_str << scanner.matched)
|
|
56
|
+
elsif scanner.skip(/(?:\\"|[^"\n\r])+/m)
|
|
53
57
|
new_query_str && (new_query_str << scanner.matched)
|
|
54
58
|
elsif scanner.skip('"')
|
|
55
59
|
new_query_str && (new_query_str << '"')
|
|
56
|
-
|
|
60
|
+
if !inside_triple_quoted_string
|
|
61
|
+
inside_single_quoted_string = !inside_single_quoted_string
|
|
62
|
+
end
|
|
57
63
|
elsif scanner.skip("\n")
|
|
58
64
|
if inside_single_quoted_string
|
|
59
65
|
new_query_str ||= query_str[0, scanner.pos - 1]
|
|
@@ -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
|
@@ -94,7 +94,7 @@ module GraphQL
|
|
|
94
94
|
end
|
|
95
95
|
|
|
96
96
|
if required == :nullable
|
|
97
|
-
self.owner.validates(required: { argument:
|
|
97
|
+
self.owner.validates(required: { argument: @keyword })
|
|
98
98
|
end
|
|
99
99
|
|
|
100
100
|
if definition_block
|
|
@@ -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?)
|
|
@@ -101,7 +101,7 @@ module GraphQL
|
|
|
101
101
|
end
|
|
102
102
|
|
|
103
103
|
child_class.ancestors.reverse_each do |ancestor|
|
|
104
|
-
if ancestor.const_defined?(:ResolverMethods)
|
|
104
|
+
if ancestor != child_class && ancestor <= GraphQL::Schema::Interface && ancestor.const_defined?(:ResolverMethods, false)
|
|
105
105
|
child_class.extend(ancestor::ResolverMethods)
|
|
106
106
|
end
|
|
107
107
|
end
|
|
@@ -105,11 +105,13 @@ module GraphQL
|
|
|
105
105
|
end
|
|
106
106
|
|
|
107
107
|
def load_constant(class_name)
|
|
108
|
-
const =
|
|
108
|
+
const = begin
|
|
109
|
+
@custom_namespace.const_get(class_name)
|
|
110
|
+
rescue NameError
|
|
111
|
+
# Dup the built-in so that the cached fields aren't shared
|
|
112
|
+
@built_in_namespace.const_get(class_name)
|
|
113
|
+
end
|
|
109
114
|
dup_type_class(const)
|
|
110
|
-
rescue NameError
|
|
111
|
-
# Dup the built-in so that the cached fields aren't shared
|
|
112
|
-
dup_type_class(@built_in_namespace.const_get(class_name))
|
|
113
115
|
end
|
|
114
116
|
|
|
115
117
|
def get_fields_from_class(class_sym:)
|
|
@@ -133,23 +135,6 @@ module GraphQL
|
|
|
133
135
|
end
|
|
134
136
|
end
|
|
135
137
|
end
|
|
136
|
-
|
|
137
|
-
class PerFieldProxyResolve
|
|
138
|
-
def initialize(object_class:, inner_resolve:)
|
|
139
|
-
@object_class = object_class
|
|
140
|
-
@inner_resolve = inner_resolve
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
def call(obj, args, ctx)
|
|
144
|
-
query_ctx = ctx.query.context
|
|
145
|
-
# Remove the QueryType wrapper
|
|
146
|
-
if obj.is_a?(GraphQL::Schema::Object)
|
|
147
|
-
obj = obj.object
|
|
148
|
-
end
|
|
149
|
-
wrapped_object = @object_class.wrap(obj, query_ctx)
|
|
150
|
-
@inner_resolve.call(wrapped_object, args, ctx)
|
|
151
|
-
end
|
|
152
|
-
end
|
|
153
138
|
end
|
|
154
139
|
end
|
|
155
140
|
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)
|