graphql 1.12.10 → 1.12.11
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.
Potentially problematic release.
This version of graphql might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/lib/graphql/backtrace/table.rb +14 -2
- data/lib/graphql/dataloader.rb +12 -14
- data/lib/graphql/execution/execute.rb +1 -1
- data/lib/graphql/execution/interpreter.rb +4 -8
- data/lib/graphql/execution/interpreter/runtime.rb +179 -156
- data/lib/graphql/introspection/schema_type.rb +1 -1
- data/lib/graphql/schema.rb +26 -200
- data/lib/graphql/schema/addition.rb +238 -0
- data/lib/graphql/schema/argument.rb +56 -39
- data/lib/graphql/schema/directive/transform.rb +13 -1
- data/lib/graphql/schema/resolver.rb +22 -2
- data/lib/graphql/version.rb +1 -1
- metadata +7 -7
- data/lib/graphql/execution/interpreter/hash_response.rb +0 -46
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9e46a535c108ff0ae582fcfa598b21cda8faad15142629b1cdae772f3d24cd7e
|
4
|
+
data.tar.gz: 3185ec249d624c6b23920d9d9fb47167ac031b7645053ff705e3c04acc0fa571
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 989c1074a6de63a31002d2bd92da719e1b08682d3eefe361add671a75c1991173b13cc6516f8ede8ff5bcf7499935e514cd8d7d57a2940e85160bc427538e99c
|
7
|
+
data.tar.gz: cb40ba585e721d28856951312755ef84bdd698a7299b1a383dd4f9db77d8b137e8d21ee9a8784b14780b4230a5a5325520d0ceadf90f6bd055781cc7159257b5
|
@@ -83,7 +83,7 @@ module GraphQL
|
|
83
83
|
value = if top && @override_value
|
84
84
|
@override_value
|
85
85
|
else
|
86
|
-
@context.query.context.namespace(:interpreter)[:runtime]
|
86
|
+
value_at(@context.query.context.namespace(:interpreter)[:runtime], context_entry.path)
|
87
87
|
end
|
88
88
|
rows << [
|
89
89
|
"#{context_entry.ast_node ? context_entry.ast_node.position.join(":") : ""}",
|
@@ -130,7 +130,7 @@ module GraphQL
|
|
130
130
|
if object.is_a?(GraphQL::Schema::Object)
|
131
131
|
object = object.object
|
132
132
|
end
|
133
|
-
value = context_entry.namespace(:interpreter)[:runtime]
|
133
|
+
value = value_at(context_entry.namespace(:interpreter)[:runtime], [])
|
134
134
|
rows << [
|
135
135
|
"#{position}",
|
136
136
|
"#{op_type}#{op_name ? " #{op_name}" : ""}",
|
@@ -142,6 +142,18 @@ module GraphQL
|
|
142
142
|
raise "Unexpected get_rows subject #{context_entry.class} (#{context_entry.inspect})"
|
143
143
|
end
|
144
144
|
end
|
145
|
+
|
146
|
+
def value_at(runtime, path)
|
147
|
+
response = runtime.response
|
148
|
+
path.each do |key|
|
149
|
+
if response && (response = response[key])
|
150
|
+
next
|
151
|
+
else
|
152
|
+
break
|
153
|
+
end
|
154
|
+
end
|
155
|
+
response
|
156
|
+
end
|
145
157
|
end
|
146
158
|
end
|
147
159
|
end
|
data/lib/graphql/dataloader.rb
CHANGED
@@ -136,26 +136,24 @@ module GraphQL
|
|
136
136
|
# This is where an evented approach would be even better -- can we tell which
|
137
137
|
# fibers are ready to continue, and continue execution there?
|
138
138
|
#
|
139
|
-
|
139
|
+
source_fiber_queue = if (first_source_fiber = create_source_fiber)
|
140
140
|
[first_source_fiber]
|
141
141
|
else
|
142
142
|
nil
|
143
143
|
end
|
144
144
|
|
145
|
-
if
|
146
|
-
|
147
|
-
# that newly-pending source will run _before_ the one that depends on it.
|
148
|
-
# (See below where the old fiber is pushed to the stack, then the new fiber is pushed on the stack.)
|
149
|
-
while (outer_source_fiber = source_fiber_stack.pop)
|
145
|
+
if source_fiber_queue
|
146
|
+
while (outer_source_fiber = source_fiber_queue.shift)
|
150
147
|
resume(outer_source_fiber)
|
151
148
|
|
152
|
-
if outer_source_fiber.alive?
|
153
|
-
source_fiber_stack << outer_source_fiber
|
154
|
-
end
|
155
149
|
# If this source caused more sources to become pending, run those before running this one again:
|
156
150
|
next_source_fiber = create_source_fiber
|
157
151
|
if next_source_fiber
|
158
|
-
|
152
|
+
source_fiber_queue << next_source_fiber
|
153
|
+
end
|
154
|
+
|
155
|
+
if outer_source_fiber.alive?
|
156
|
+
source_fiber_queue << outer_source_fiber
|
159
157
|
end
|
160
158
|
end
|
161
159
|
end
|
@@ -224,16 +222,16 @@ module GraphQL
|
|
224
222
|
#
|
225
223
|
# @see https://github.com/rmosolgo/graphql-ruby/issues/3449
|
226
224
|
def spawn_fiber
|
227
|
-
fiber_locals = {}
|
225
|
+
fiber_locals = {}
|
228
226
|
|
229
227
|
Thread.current.keys.each do |fiber_var_key|
|
230
228
|
fiber_locals[fiber_var_key] = Thread.current[fiber_var_key]
|
231
|
-
end
|
229
|
+
end
|
232
230
|
|
233
|
-
Fiber.new do
|
231
|
+
Fiber.new do
|
234
232
|
fiber_locals.each { |k, v| Thread.current[k] = v }
|
235
233
|
yield
|
236
|
-
end
|
234
|
+
end
|
237
235
|
end
|
238
236
|
end
|
239
237
|
end
|
@@ -4,7 +4,6 @@ require "graphql/execution/interpreter/argument_value"
|
|
4
4
|
require "graphql/execution/interpreter/arguments"
|
5
5
|
require "graphql/execution/interpreter/arguments_cache"
|
6
6
|
require "graphql/execution/interpreter/execution_errors"
|
7
|
-
require "graphql/execution/interpreter/hash_response"
|
8
7
|
require "graphql/execution/interpreter/runtime"
|
9
8
|
require "graphql/execution/interpreter/resolve"
|
10
9
|
require "graphql/execution/interpreter/handles_raw_value"
|
@@ -19,7 +18,7 @@ module GraphQL
|
|
19
18
|
def execute(_operation, _root_type, query)
|
20
19
|
runtime = evaluate(query)
|
21
20
|
sync_lazies(query: query)
|
22
|
-
runtime.
|
21
|
+
runtime.response
|
23
22
|
end
|
24
23
|
|
25
24
|
def self.use(schema_class)
|
@@ -57,7 +56,7 @@ module GraphQL
|
|
57
56
|
|
58
57
|
def self.finish_query(query, _multiplex)
|
59
58
|
{
|
60
|
-
"data" => query.context.namespace(:interpreter)[:runtime].
|
59
|
+
"data" => query.context.namespace(:interpreter)[:runtime].response
|
61
60
|
}
|
62
61
|
end
|
63
62
|
|
@@ -67,10 +66,7 @@ module GraphQL
|
|
67
66
|
# Although queries in a multiplex _share_ an Interpreter instance,
|
68
67
|
# they also have another item of state, which is private to that query
|
69
68
|
# in particular, assign it here:
|
70
|
-
runtime = Runtime.new(
|
71
|
-
query: query,
|
72
|
-
response: HashResponse.new,
|
73
|
-
)
|
69
|
+
runtime = Runtime.new(query: query)
|
74
70
|
query.context.namespace(:interpreter)[:runtime] = runtime
|
75
71
|
|
76
72
|
query.trace("execute_query", {query: query}) do
|
@@ -91,7 +87,7 @@ module GraphQL
|
|
91
87
|
final_values = queries.map do |query|
|
92
88
|
runtime = query.context.namespace(:interpreter)[:runtime]
|
93
89
|
# it might not be present if the query has an error
|
94
|
-
runtime ? runtime.
|
90
|
+
runtime ? runtime.response : nil
|
95
91
|
end
|
96
92
|
final_values.compact!
|
97
93
|
tracer.trace("execute_query_lazy", {multiplex: multiplex, query: query}) do
|
@@ -8,6 +8,28 @@ module GraphQL
|
|
8
8
|
#
|
9
9
|
# @api private
|
10
10
|
class Runtime
|
11
|
+
|
12
|
+
module GraphQLResult
|
13
|
+
# These methods are private concerns of GraphQL-Ruby,
|
14
|
+
# they aren't guaranteed to continue working in the future.
|
15
|
+
attr_accessor :graphql_dead, :graphql_parent, :graphql_result_name
|
16
|
+
# Although these are used by only one of the Result classes,
|
17
|
+
# it's handy to have the methods implemented on both (even though they just return `nil`)
|
18
|
+
# because it makes it easy to check if anything is assigned.
|
19
|
+
# @return [nil, Array<String>]
|
20
|
+
attr_accessor :graphql_non_null_field_names
|
21
|
+
# @return [nil, true]
|
22
|
+
attr_accessor :graphql_non_null_list_items
|
23
|
+
end
|
24
|
+
|
25
|
+
class GraphQLResultHash < Hash
|
26
|
+
include GraphQLResult
|
27
|
+
end
|
28
|
+
|
29
|
+
class GraphQLResultArray < Array
|
30
|
+
include GraphQLResult
|
31
|
+
end
|
32
|
+
|
11
33
|
# @return [GraphQL::Query]
|
12
34
|
attr_reader :query
|
13
35
|
|
@@ -17,24 +39,23 @@ module GraphQL
|
|
17
39
|
# @return [GraphQL::Query::Context]
|
18
40
|
attr_reader :context
|
19
41
|
|
20
|
-
|
42
|
+
# @return [Hash]
|
43
|
+
attr_reader :response
|
44
|
+
|
45
|
+
def initialize(query:)
|
21
46
|
@query = query
|
22
47
|
@dataloader = query.multiplex.dataloader
|
23
48
|
@schema = query.schema
|
24
49
|
@context = query.context
|
25
50
|
@multiplex_context = query.multiplex.context
|
26
51
|
@interpreter_context = @context.namespace(:interpreter)
|
27
|
-
@response =
|
28
|
-
@dead_paths = {}
|
29
|
-
@types_at_paths = {}
|
52
|
+
@response = GraphQLResultHash.new
|
30
53
|
# A cache of { Class => { String => Schema::Field } }
|
31
54
|
# Which assumes that MyObject.get_field("myField") will return the same field
|
32
55
|
# during the lifetime of a query
|
33
56
|
@fields_cache = Hash.new { |h, k| h[k] = {} }
|
34
|
-
|
35
|
-
|
36
|
-
def final_value
|
37
|
-
@response.final_value
|
57
|
+
# { Class => Boolean }
|
58
|
+
@lazy_cache = {}
|
38
59
|
end
|
39
60
|
|
40
61
|
def inspect
|
@@ -55,7 +76,7 @@ module GraphQL
|
|
55
76
|
|
56
77
|
if object_proxy.nil?
|
57
78
|
# Root .authorized? returned false.
|
58
|
-
|
79
|
+
@response = nil
|
59
80
|
else
|
60
81
|
resolve_with_directives(object_proxy, root_operation) do # execute query level directives
|
61
82
|
gathered_selections = gather_selections(object_proxy, root_type, root_operation.selections)
|
@@ -68,6 +89,7 @@ module GraphQL
|
|
68
89
|
root_type,
|
69
90
|
root_op_type == "mutation",
|
70
91
|
gathered_selections,
|
92
|
+
@response,
|
71
93
|
)
|
72
94
|
}
|
73
95
|
end
|
@@ -137,13 +159,13 @@ module GraphQL
|
|
137
159
|
NO_ARGS = {}.freeze
|
138
160
|
|
139
161
|
# @return [void]
|
140
|
-
def evaluate_selections(path, scoped_context, owner_object, owner_type, is_eager_selection, gathered_selections)
|
162
|
+
def evaluate_selections(path, scoped_context, owner_object, owner_type, is_eager_selection, gathered_selections, selections_result)
|
141
163
|
set_all_interpreter_context(owner_object, nil, nil, path)
|
142
164
|
|
143
165
|
gathered_selections.each do |result_name, field_ast_nodes_or_ast_node|
|
144
166
|
@dataloader.append_job {
|
145
167
|
evaluate_selection(
|
146
|
-
path, result_name, field_ast_nodes_or_ast_node, scoped_context, owner_object, owner_type, is_eager_selection
|
168
|
+
path, result_name, field_ast_nodes_or_ast_node, scoped_context, owner_object, owner_type, is_eager_selection, selections_result
|
147
169
|
)
|
148
170
|
}
|
149
171
|
end
|
@@ -154,7 +176,7 @@ module GraphQL
|
|
154
176
|
attr_reader :progress_path
|
155
177
|
|
156
178
|
# @return [void]
|
157
|
-
def evaluate_selection(path, result_name, field_ast_nodes_or_ast_node, scoped_context, owner_object, owner_type, is_eager_field)
|
179
|
+
def evaluate_selection(path, result_name, field_ast_nodes_or_ast_node, scoped_context, owner_object, owner_type, is_eager_field, selections_result) # rubocop:disable Metrics/ParameterLists
|
158
180
|
# As a performance optimization, the hash key will be a `Node` if
|
159
181
|
# there's only one selection of the field. But if there are multiple
|
160
182
|
# selections of the field, it will be an Array of nodes
|
@@ -188,7 +210,9 @@ module GraphQL
|
|
188
210
|
# This seems janky, but we need to know
|
189
211
|
# the field's return type at this path in order
|
190
212
|
# to propagate `null`
|
191
|
-
|
213
|
+
if return_type.non_null?
|
214
|
+
(selections_result.graphql_non_null_field_names ||= []).push(result_name)
|
215
|
+
end
|
192
216
|
# Set this before calling `run_with_directives`, so that the directive can have the latest path
|
193
217
|
set_all_interpreter_context(nil, field_defn, nil, next_path)
|
194
218
|
|
@@ -202,21 +226,21 @@ module GraphQL
|
|
202
226
|
total_args_count = field_defn.arguments.size
|
203
227
|
if total_args_count == 0
|
204
228
|
kwarg_arguments = GraphQL::Execution::Interpreter::Arguments::EMPTY
|
205
|
-
evaluate_selection_with_args(kwarg_arguments, field_defn, next_path, ast_node, field_ast_nodes, scoped_context, owner_type, object, is_eager_field)
|
229
|
+
evaluate_selection_with_args(kwarg_arguments, field_defn, next_path, ast_node, field_ast_nodes, scoped_context, owner_type, object, is_eager_field, result_name, selections_result)
|
206
230
|
else
|
207
231
|
# TODO remove all arguments(...) usages?
|
208
232
|
@query.arguments_cache.dataload_for(ast_node, field_defn, object) do |resolved_arguments|
|
209
|
-
evaluate_selection_with_args(resolved_arguments, field_defn, next_path, ast_node, field_ast_nodes, scoped_context, owner_type, object, is_eager_field)
|
233
|
+
evaluate_selection_with_args(resolved_arguments, field_defn, next_path, ast_node, field_ast_nodes, scoped_context, owner_type, object, is_eager_field, result_name, selections_result)
|
210
234
|
end
|
211
235
|
end
|
212
236
|
end
|
213
237
|
|
214
|
-
def evaluate_selection_with_args(kwarg_arguments, field_defn, next_path, ast_node, field_ast_nodes, scoped_context, owner_type, object, is_eager_field) # rubocop:disable Metrics/ParameterLists
|
238
|
+
def evaluate_selection_with_args(kwarg_arguments, field_defn, next_path, ast_node, field_ast_nodes, scoped_context, owner_type, object, is_eager_field, result_name, selection_result) # rubocop:disable Metrics/ParameterLists
|
215
239
|
context.scoped_context = scoped_context
|
216
240
|
return_type = field_defn.type
|
217
|
-
after_lazy(kwarg_arguments, owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, scoped_context: context.scoped_context, owner_object: object, arguments: kwarg_arguments) do |resolved_arguments|
|
241
|
+
after_lazy(kwarg_arguments, owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, scoped_context: context.scoped_context, owner_object: object, arguments: kwarg_arguments, result_name: result_name, result: selection_result) do |resolved_arguments|
|
218
242
|
if resolved_arguments.is_a?(GraphQL::ExecutionError) || resolved_arguments.is_a?(GraphQL::UnauthorizedError)
|
219
|
-
continue_value(next_path, resolved_arguments, owner_type, field_defn, return_type.non_null?, ast_node)
|
243
|
+
continue_value(next_path, resolved_arguments, owner_type, field_defn, return_type.non_null?, ast_node, result_name, selection_result)
|
220
244
|
next
|
221
245
|
end
|
222
246
|
|
@@ -284,10 +308,10 @@ module GraphQL
|
|
284
308
|
rescue GraphQL::ExecutionError => err
|
285
309
|
err
|
286
310
|
end
|
287
|
-
after_lazy(app_result, owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, scoped_context: context.scoped_context, owner_object: object, arguments: kwarg_arguments) do |inner_result|
|
288
|
-
continue_value = continue_value(next_path, inner_result, owner_type, field_defn, return_type.non_null?, ast_node)
|
311
|
+
after_lazy(app_result, owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, scoped_context: context.scoped_context, owner_object: object, arguments: kwarg_arguments, result_name: result_name, result: selection_result) do |inner_result|
|
312
|
+
continue_value = continue_value(next_path, inner_result, owner_type, field_defn, return_type.non_null?, ast_node, result_name, selection_result)
|
289
313
|
if HALT != continue_value
|
290
|
-
continue_field(next_path, continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, kwarg_arguments)
|
314
|
+
continue_field(next_path, continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, kwarg_arguments, result_name, selection_result)
|
291
315
|
end
|
292
316
|
end
|
293
317
|
end
|
@@ -304,43 +328,109 @@ module GraphQL
|
|
304
328
|
end
|
305
329
|
end
|
306
330
|
|
331
|
+
def dead_result?(selection_result)
|
332
|
+
r = selection_result
|
333
|
+
while r
|
334
|
+
if r.graphql_dead
|
335
|
+
return true
|
336
|
+
else
|
337
|
+
r = r.graphql_parent
|
338
|
+
end
|
339
|
+
end
|
340
|
+
false
|
341
|
+
end
|
342
|
+
|
343
|
+
def set_result(selection_result, result_name, value)
|
344
|
+
if !dead_result?(selection_result)
|
345
|
+
if value.nil? &&
|
346
|
+
( # there are two conditions under which `nil` is not allowed in the response:
|
347
|
+
(selection_result.graphql_non_null_list_items) || # this value would be written into a list that doesn't allow nils
|
348
|
+
((nn = selection_result.graphql_non_null_field_names) && nn.include?(result_name)) # this value would be written into a field that doesn't allow nils
|
349
|
+
)
|
350
|
+
# This is an invalid nil that should be propagated
|
351
|
+
# One caller of this method passes a block,
|
352
|
+
# namely when application code returns a `nil` to GraphQL and it doesn't belong there.
|
353
|
+
# The other possibility for reaching here is when a field returns an ExecutionError, so we write
|
354
|
+
# `nil` to the response, not knowing whether it's an invalid `nil` or not.
|
355
|
+
# (And in that case, we don't have to call the schema's handler, since it's not a bug in the application.)
|
356
|
+
# TODO the code is trying to tell me something.
|
357
|
+
yield if block_given?
|
358
|
+
parent = selection_result.graphql_parent
|
359
|
+
name_in_parent = selection_result.graphql_result_name
|
360
|
+
if parent.nil? # This is a top-level result hash
|
361
|
+
@response = nil
|
362
|
+
else
|
363
|
+
set_result(parent, name_in_parent, nil)
|
364
|
+
# This is odd, but it's how it used to work. Even if `parent` _would_ accept
|
365
|
+
# a `nil`, it's marked dead. TODO: check the spec, is there a reason for this?
|
366
|
+
parent.graphql_dead = true
|
367
|
+
end
|
368
|
+
else
|
369
|
+
selection_result[result_name] = value
|
370
|
+
end
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
307
374
|
HALT = Object.new
|
308
|
-
def continue_value(path, value, parent_type, field, is_non_null, ast_node)
|
309
|
-
|
375
|
+
def continue_value(path, value, parent_type, field, is_non_null, ast_node, result_name, selection_result) # rubocop:disable Metrics/ParameterLists
|
376
|
+
case value
|
377
|
+
when nil
|
310
378
|
if is_non_null
|
311
|
-
|
312
|
-
|
379
|
+
set_result(selection_result, result_name, nil) do
|
380
|
+
# This block is called if `result_name` is not dead. (Maybe a previous invalid nil caused it be marked dead.)
|
381
|
+
err = parent_type::InvalidNullError.new(parent_type, field, value)
|
382
|
+
schema.type_error(err, context)
|
383
|
+
end
|
313
384
|
else
|
314
|
-
|
385
|
+
set_result(selection_result, result_name, nil)
|
315
386
|
end
|
316
387
|
HALT
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
388
|
+
when GraphQL::Error
|
389
|
+
# Handle these cases inside a single `when`
|
390
|
+
# to avoid the overhead of checking three different classes
|
391
|
+
# every time.
|
392
|
+
if value.is_a?(GraphQL::ExecutionError)
|
393
|
+
if !dead_result?(selection_result)
|
394
|
+
value.path ||= path
|
395
|
+
value.ast_node ||= ast_node
|
396
|
+
context.errors << value
|
397
|
+
set_result(selection_result, result_name, nil)
|
398
|
+
end
|
399
|
+
HALT
|
400
|
+
elsif value.is_a?(GraphQL::UnauthorizedError)
|
401
|
+
# this hook might raise & crash, or it might return
|
402
|
+
# a replacement value
|
403
|
+
next_value = begin
|
404
|
+
schema.unauthorized_object(value)
|
405
|
+
rescue GraphQL::ExecutionError => err
|
406
|
+
err
|
407
|
+
end
|
408
|
+
continue_value(path, next_value, parent_type, field, is_non_null, ast_node, result_name, selection_result)
|
409
|
+
elsif GraphQL::Execution::Execute::SKIP == value
|
410
|
+
HALT
|
411
|
+
else
|
412
|
+
# What could this actually _be_? Anyhow,
|
413
|
+
# preserve the default behavior of doing nothing with it.
|
414
|
+
value
|
326
415
|
end
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
416
|
+
when Array
|
417
|
+
# It's an array full of execution errors; add them all.
|
418
|
+
if value.any? && value.all? { |v| v.is_a?(GraphQL::ExecutionError) }
|
419
|
+
if !dead_result?(selection_result)
|
420
|
+
value.each_with_index do |error, index|
|
421
|
+
error.ast_node ||= ast_node
|
422
|
+
error.path ||= path + (field.type.list? ? [index] : [])
|
423
|
+
context.errors << error
|
424
|
+
end
|
425
|
+
set_result(selection_result, result_name, nil)
|
426
|
+
end
|
427
|
+
HALT
|
428
|
+
else
|
429
|
+
value
|
336
430
|
end
|
337
|
-
|
338
|
-
continue_value(path, next_value, parent_type, field, is_non_null, ast_node)
|
339
|
-
elsif GraphQL::Execution::Execute::SKIP == value
|
340
|
-
HALT
|
341
|
-
elsif value.is_a?(GraphQL::Execution::Interpreter::RawValue)
|
431
|
+
when GraphQL::Execution::Interpreter::RawValue
|
342
432
|
# Write raw value directly to the response without resolving nested objects
|
343
|
-
|
433
|
+
set_result(selection_result, result_name, value.resolve)
|
344
434
|
HALT
|
345
435
|
else
|
346
436
|
value
|
@@ -355,17 +445,22 @@ module GraphQL
|
|
355
445
|
# Location information from `path` and `ast_node`.
|
356
446
|
#
|
357
447
|
# @return [Lazy, Array, Hash, Object] Lazy, Array, and Hash are all traversed to resolve lazy values later
|
358
|
-
def continue_field(path, value, owner_type, field, current_type, ast_node, next_selections, is_non_null, owner_object, arguments) # rubocop:disable Metrics/ParameterLists
|
448
|
+
def continue_field(path, value, owner_type, field, current_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result) # rubocop:disable Metrics/ParameterLists
|
449
|
+
if current_type.non_null?
|
450
|
+
current_type = current_type.of_type
|
451
|
+
is_non_null = true
|
452
|
+
end
|
453
|
+
|
359
454
|
case current_type.kind.name
|
360
455
|
when "SCALAR", "ENUM"
|
361
456
|
r = current_type.coerce_result(value, context)
|
362
|
-
|
457
|
+
set_result(selection_result, result_name, r)
|
363
458
|
r
|
364
459
|
when "UNION", "INTERFACE"
|
365
460
|
resolved_type_or_lazy, resolved_value = resolve_type(current_type, value, path)
|
366
461
|
resolved_value ||= value
|
367
462
|
|
368
|
-
after_lazy(resolved_type_or_lazy, owner: current_type, path: path, ast_node: ast_node, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments, trace: false) do |resolved_type|
|
463
|
+
after_lazy(resolved_type_or_lazy, owner: current_type, path: path, ast_node: ast_node, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result) do |resolved_type|
|
369
464
|
possible_types = query.possible_types(current_type)
|
370
465
|
|
371
466
|
if !possible_types.include?(resolved_type)
|
@@ -373,10 +468,10 @@ module GraphQL
|
|
373
468
|
err_class = current_type::UnresolvedTypeError
|
374
469
|
type_error = err_class.new(resolved_value, field, parent_type, resolved_type, possible_types)
|
375
470
|
schema.type_error(type_error, context)
|
376
|
-
|
471
|
+
set_result(selection_result, result_name, nil)
|
377
472
|
nil
|
378
473
|
else
|
379
|
-
continue_field(path, resolved_value, owner_type, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments)
|
474
|
+
continue_field(path, resolved_value, owner_type, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result)
|
380
475
|
end
|
381
476
|
end
|
382
477
|
when "OBJECT"
|
@@ -385,34 +480,40 @@ module GraphQL
|
|
385
480
|
rescue GraphQL::ExecutionError => err
|
386
481
|
err
|
387
482
|
end
|
388
|
-
after_lazy(object_proxy, owner: current_type, path: path, ast_node: ast_node, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments, trace: false) do |inner_object|
|
389
|
-
continue_value = continue_value(path, inner_object, owner_type, field, is_non_null, ast_node)
|
483
|
+
after_lazy(object_proxy, owner: current_type, path: path, ast_node: ast_node, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result) do |inner_object|
|
484
|
+
continue_value = continue_value(path, inner_object, owner_type, field, is_non_null, ast_node, result_name, selection_result)
|
390
485
|
if HALT != continue_value
|
391
|
-
response_hash =
|
392
|
-
|
486
|
+
response_hash = GraphQLResultHash.new
|
487
|
+
response_hash.graphql_parent = selection_result
|
488
|
+
response_hash.graphql_result_name = result_name
|
489
|
+
set_result(selection_result, result_name, response_hash)
|
393
490
|
gathered_selections = gather_selections(continue_value, current_type, next_selections)
|
394
|
-
evaluate_selections(path, context.scoped_context, continue_value, current_type, false, gathered_selections)
|
491
|
+
evaluate_selections(path, context.scoped_context, continue_value, current_type, false, gathered_selections, response_hash)
|
395
492
|
response_hash
|
396
493
|
end
|
397
494
|
end
|
398
495
|
when "LIST"
|
399
|
-
response_list = []
|
400
|
-
write_in_response(path, response_list)
|
401
496
|
inner_type = current_type.of_type
|
497
|
+
response_list = GraphQLResultArray.new
|
498
|
+
response_list.graphql_non_null_list_items = inner_type.non_null?
|
499
|
+
response_list.graphql_parent = selection_result
|
500
|
+
response_list.graphql_result_name = result_name
|
501
|
+
set_result(selection_result, result_name, response_list)
|
502
|
+
|
402
503
|
idx = 0
|
403
504
|
scoped_context = context.scoped_context
|
404
505
|
begin
|
405
506
|
value.each do |inner_value|
|
406
507
|
next_path = path.dup
|
407
508
|
next_path << idx
|
509
|
+
this_idx = idx
|
408
510
|
next_path.freeze
|
409
511
|
idx += 1
|
410
|
-
set_type_at_path(next_path, inner_type)
|
411
512
|
# This will update `response_list` with the lazy
|
412
|
-
after_lazy(inner_value, owner: inner_type, path: next_path, ast_node: ast_node, scoped_context: scoped_context, field: field, owner_object: owner_object, arguments: arguments) do |inner_inner_value|
|
413
|
-
continue_value = continue_value(next_path, inner_inner_value, owner_type, field, inner_type.non_null?, ast_node)
|
513
|
+
after_lazy(inner_value, owner: inner_type, path: next_path, ast_node: ast_node, scoped_context: scoped_context, field: field, owner_object: owner_object, arguments: arguments, result_name: this_idx, result: response_list) do |inner_inner_value|
|
514
|
+
continue_value = continue_value(next_path, inner_inner_value, owner_type, field, inner_type.non_null?, ast_node, this_idx, response_list)
|
414
515
|
if HALT != continue_value
|
415
|
-
continue_field(next_path, continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments)
|
516
|
+
continue_field(next_path, continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments, this_idx, response_list)
|
416
517
|
end
|
417
518
|
end
|
418
519
|
end
|
@@ -428,11 +529,6 @@ module GraphQL
|
|
428
529
|
end
|
429
530
|
|
430
531
|
response_list
|
431
|
-
when "NON_NULL"
|
432
|
-
inner_type = current_type.of_type
|
433
|
-
# Don't `set_type_at_path` because we want the static type,
|
434
|
-
# we're going to use that to determine whether a `nil` should be propagated or not.
|
435
|
-
continue_field(path, value, owner_type, field, inner_type, ast_node, next_selections, true, owner_object, arguments)
|
436
532
|
else
|
437
533
|
raise "Invariant: Unhandled type kind #{current_type.kind} (#{current_type})"
|
438
534
|
end
|
@@ -492,9 +588,8 @@ module GraphQL
|
|
492
588
|
# @param eager [Boolean] Set to `true` for mutation root fields only
|
493
589
|
# @param trace [Boolean] If `false`, don't wrap this with field tracing
|
494
590
|
# @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.
|
495
|
-
def after_lazy(lazy_obj, owner:, field:, path:, scoped_context:, owner_object:, arguments:, ast_node:, eager: false, trace: true, &block)
|
496
|
-
|
497
|
-
if schema.lazy?(lazy_obj)
|
591
|
+
def after_lazy(lazy_obj, owner:, field:, path:, scoped_context:, owner_object:, arguments:, ast_node:, result:, result_name:, eager: false, trace: true, &block)
|
592
|
+
if lazy?(lazy_obj)
|
498
593
|
lazy = GraphQL::Execution::Lazy.new(path: path, field: field) do
|
499
594
|
set_all_interpreter_context(owner_object, field, arguments, path)
|
500
595
|
context.scoped_context = scoped_context
|
@@ -513,16 +608,17 @@ module GraphQL
|
|
513
608
|
rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => err
|
514
609
|
err
|
515
610
|
end
|
516
|
-
|
611
|
+
yield(inner_obj)
|
517
612
|
end
|
518
613
|
|
519
614
|
if eager
|
520
615
|
lazy.value
|
521
616
|
else
|
522
|
-
|
617
|
+
set_result(result, result_name, lazy)
|
523
618
|
lazy
|
524
619
|
end
|
525
620
|
else
|
621
|
+
set_all_interpreter_context(owner_object, field, arguments, path)
|
526
622
|
yield(lazy_obj)
|
527
623
|
end
|
528
624
|
end
|
@@ -536,85 +632,6 @@ module GraphQL
|
|
536
632
|
end
|
537
633
|
end
|
538
634
|
|
539
|
-
def write_invalid_null_in_response(path, invalid_null_error)
|
540
|
-
if !dead_path?(path)
|
541
|
-
schema.type_error(invalid_null_error, context)
|
542
|
-
write_in_response(path, nil)
|
543
|
-
add_dead_path(path)
|
544
|
-
end
|
545
|
-
end
|
546
|
-
|
547
|
-
def write_execution_errors_in_response(path, errors)
|
548
|
-
if !dead_path?(path)
|
549
|
-
errors.each do |v|
|
550
|
-
context.errors << v
|
551
|
-
end
|
552
|
-
write_in_response(path, nil)
|
553
|
-
add_dead_path(path)
|
554
|
-
end
|
555
|
-
end
|
556
|
-
|
557
|
-
def write_in_response(path, value)
|
558
|
-
if dead_path?(path)
|
559
|
-
return
|
560
|
-
else
|
561
|
-
if value.nil? && path.any? && type_at(path).non_null?
|
562
|
-
# This nil is invalid, try writing it at the previous spot
|
563
|
-
propagate_path = path[0..-2]
|
564
|
-
write_in_response(propagate_path, value)
|
565
|
-
add_dead_path(propagate_path)
|
566
|
-
else
|
567
|
-
@response.write(path, value)
|
568
|
-
end
|
569
|
-
end
|
570
|
-
end
|
571
|
-
|
572
|
-
def value_at(path)
|
573
|
-
i = 0
|
574
|
-
value = @response.final_value
|
575
|
-
while value && (part = path[i])
|
576
|
-
value = value[part]
|
577
|
-
i += 1
|
578
|
-
end
|
579
|
-
value
|
580
|
-
end
|
581
|
-
|
582
|
-
# To propagate nulls, we have to know what the field type was
|
583
|
-
# at previous parts of the response.
|
584
|
-
# This hash matches the response
|
585
|
-
def type_at(path)
|
586
|
-
@types_at_paths.fetch(path)
|
587
|
-
end
|
588
|
-
|
589
|
-
def set_type_at_path(path, type)
|
590
|
-
@types_at_paths[path] = type
|
591
|
-
nil
|
592
|
-
end
|
593
|
-
|
594
|
-
# Mark `path` as having been permanently nulled out.
|
595
|
-
# No values will be added beyond that path.
|
596
|
-
def add_dead_path(path)
|
597
|
-
dead = @dead_paths
|
598
|
-
path.each do |part|
|
599
|
-
dead = dead[part] ||= {}
|
600
|
-
end
|
601
|
-
dead[:__dead] = true
|
602
|
-
end
|
603
|
-
|
604
|
-
def dead_path?(path)
|
605
|
-
res = @dead_paths
|
606
|
-
path.each do |part|
|
607
|
-
if res
|
608
|
-
if res[:__dead]
|
609
|
-
break
|
610
|
-
else
|
611
|
-
res = res[part]
|
612
|
-
end
|
613
|
-
end
|
614
|
-
end
|
615
|
-
res && res[:__dead]
|
616
|
-
end
|
617
|
-
|
618
635
|
# Set this pair in the Query context, but also in the interpeter namespace,
|
619
636
|
# for compatibility.
|
620
637
|
def set_interpreter_context(key, value)
|
@@ -633,7 +650,7 @@ module GraphQL
|
|
633
650
|
query.resolve_type(type, value)
|
634
651
|
end
|
635
652
|
|
636
|
-
if
|
653
|
+
if lazy?(resolved_type)
|
637
654
|
GraphQL::Execution::Lazy.new do
|
638
655
|
query.trace("resolve_type_lazy", trace_payload) do
|
639
656
|
schema.sync_lazy(resolved_type)
|
@@ -647,6 +664,12 @@ module GraphQL
|
|
647
664
|
def authorized_new(type, value, context)
|
648
665
|
type.authorized_new(value, context)
|
649
666
|
end
|
667
|
+
|
668
|
+
def lazy?(object)
|
669
|
+
@lazy_cache.fetch(object.class) {
|
670
|
+
@lazy_cache[object.class] = @schema.lazy?(object)
|
671
|
+
}
|
672
|
+
end
|
650
673
|
end
|
651
674
|
end
|
652
675
|
end
|