graphql 1.12.10 → 1.12.12
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 +27 -14
- data/lib/graphql/dataloader/null_dataloader.rb +1 -0
- data/lib/graphql/execution/execute.rb +1 -1
- data/lib/graphql/execution/interpreter.rb +4 -8
- data/lib/graphql/execution/interpreter/arguments_cache.rb +3 -2
- data/lib/graphql/execution/interpreter/runtime.rb +368 -204
- 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 +21 -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: 296954a3b03f2fd5dae9fae56eaa08ea04b3dfb8f25923201ea27c67147c71fa
|
4
|
+
data.tar.gz: 4de323637c29b729a3eb3f07b620e2014515b37235c485002d016ead490d86e7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fdf50f6c6f170aa4c50d46356e4089c40a939bf3d0dfd4ab7bdb667d169f01a38320c971cdec1dde32353a20823c1756196312df5e425af41a157caa1179e845
|
7
|
+
data.tar.gz: 4202ca4741998ee1f5535eea14ff93c1a8529ff5085625d67171e75736f65f55d0433b93512c272e16f4f9a48dd630eba49cf37a54e49b0e9e3dad5f0496930c
|
@@ -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
@@ -77,6 +77,21 @@ module GraphQL
|
|
77
77
|
nil
|
78
78
|
end
|
79
79
|
|
80
|
+
# Use a self-contained queue for the work in the block.
|
81
|
+
def run_isolated
|
82
|
+
prev_queue = @pending_jobs
|
83
|
+
@pending_jobs = []
|
84
|
+
res = nil
|
85
|
+
# Make sure the block is inside a Fiber, so it can `Fiber.yield`
|
86
|
+
append_job {
|
87
|
+
res = yield
|
88
|
+
}
|
89
|
+
run
|
90
|
+
res
|
91
|
+
ensure
|
92
|
+
@pending_jobs = prev_queue
|
93
|
+
end
|
94
|
+
|
80
95
|
# @api private Move along, move along
|
81
96
|
def run
|
82
97
|
# At a high level, the algorithm is:
|
@@ -136,26 +151,24 @@ module GraphQL
|
|
136
151
|
# This is where an evented approach would be even better -- can we tell which
|
137
152
|
# fibers are ready to continue, and continue execution there?
|
138
153
|
#
|
139
|
-
|
154
|
+
source_fiber_queue = if (first_source_fiber = create_source_fiber)
|
140
155
|
[first_source_fiber]
|
141
156
|
else
|
142
157
|
nil
|
143
158
|
end
|
144
159
|
|
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)
|
160
|
+
if source_fiber_queue
|
161
|
+
while (outer_source_fiber = source_fiber_queue.shift)
|
150
162
|
resume(outer_source_fiber)
|
151
163
|
|
152
|
-
if outer_source_fiber.alive?
|
153
|
-
source_fiber_stack << outer_source_fiber
|
154
|
-
end
|
155
164
|
# If this source caused more sources to become pending, run those before running this one again:
|
156
165
|
next_source_fiber = create_source_fiber
|
157
166
|
if next_source_fiber
|
158
|
-
|
167
|
+
source_fiber_queue << next_source_fiber
|
168
|
+
end
|
169
|
+
|
170
|
+
if outer_source_fiber.alive?
|
171
|
+
source_fiber_queue << outer_source_fiber
|
159
172
|
end
|
160
173
|
end
|
161
174
|
end
|
@@ -224,16 +237,16 @@ module GraphQL
|
|
224
237
|
#
|
225
238
|
# @see https://github.com/rmosolgo/graphql-ruby/issues/3449
|
226
239
|
def spawn_fiber
|
227
|
-
fiber_locals = {}
|
240
|
+
fiber_locals = {}
|
228
241
|
|
229
242
|
Thread.current.keys.each do |fiber_var_key|
|
230
243
|
fiber_locals[fiber_var_key] = Thread.current[fiber_var_key]
|
231
|
-
end
|
244
|
+
end
|
232
245
|
|
233
|
-
Fiber.new do
|
246
|
+
Fiber.new do
|
234
247
|
fiber_locals.each { |k, v| Thread.current[k] = v }
|
235
248
|
yield
|
236
|
-
end
|
249
|
+
end
|
237
250
|
end
|
238
251
|
end
|
239
252
|
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
|
@@ -28,11 +28,12 @@ module GraphQL
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def fetch(ast_node, argument_owner, parent_object)
|
31
|
-
@storage[ast_node][argument_owner][parent_object]
|
32
31
|
# If any jobs were enqueued, run them now,
|
33
32
|
# since this might have been called outside of execution.
|
34
33
|
# (The jobs are responsible for updating `result` in-place.)
|
35
|
-
@dataloader.
|
34
|
+
@dataloader.run_isolated do
|
35
|
+
@storage[ast_node][argument_owner][parent_object]
|
36
|
+
end
|
36
37
|
# Ack, the _hash_ is updated, but the key is eventually
|
37
38
|
# overridden with an immutable arguments instance.
|
38
39
|
# The first call queues up the job,
|
@@ -8,6 +8,49 @@ 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
|
+
|
28
|
+
attr_accessor :graphql_merged_into
|
29
|
+
|
30
|
+
def []=(key, value)
|
31
|
+
# This is a hack.
|
32
|
+
# Basically, this object is merged into the root-level result at some point.
|
33
|
+
# But the problem is, some lazies are created whose closures retain reference to _this_
|
34
|
+
# object. When those lazies are resolved, they cause an update to this object.
|
35
|
+
#
|
36
|
+
# In order to return a proper top-level result, we have to update that top-level result object.
|
37
|
+
# In order to return a proper partial result (eg, for a directive), we have to update this object, too.
|
38
|
+
# Yowza.
|
39
|
+
if (t = @graphql_merged_into)
|
40
|
+
t[key] = value
|
41
|
+
end
|
42
|
+
super
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class GraphQLResultArray < Array
|
47
|
+
include GraphQLResult
|
48
|
+
end
|
49
|
+
|
50
|
+
class GraphQLSelectionSet < Hash
|
51
|
+
attr_accessor :graphql_directives
|
52
|
+
end
|
53
|
+
|
11
54
|
# @return [GraphQL::Query]
|
12
55
|
attr_reader :query
|
13
56
|
|
@@ -17,30 +60,47 @@ module GraphQL
|
|
17
60
|
# @return [GraphQL::Query::Context]
|
18
61
|
attr_reader :context
|
19
62
|
|
20
|
-
|
63
|
+
# @return [Hash]
|
64
|
+
attr_reader :response
|
65
|
+
|
66
|
+
def initialize(query:)
|
21
67
|
@query = query
|
22
68
|
@dataloader = query.multiplex.dataloader
|
23
69
|
@schema = query.schema
|
24
70
|
@context = query.context
|
25
71
|
@multiplex_context = query.multiplex.context
|
26
72
|
@interpreter_context = @context.namespace(:interpreter)
|
27
|
-
@response =
|
28
|
-
|
29
|
-
@
|
73
|
+
@response = GraphQLResultHash.new
|
74
|
+
# Identify runtime directives by checking which of this schema's directives have overridden `def self.resolve`
|
75
|
+
@runtime_directive_names = []
|
76
|
+
noop_resolve_owner = GraphQL::Schema::Directive.singleton_class
|
77
|
+
schema.directives.each do |name, dir_defn|
|
78
|
+
if dir_defn.method(:resolve).owner != noop_resolve_owner
|
79
|
+
@runtime_directive_names << name
|
80
|
+
end
|
81
|
+
end
|
30
82
|
# A cache of { Class => { String => Schema::Field } }
|
31
83
|
# Which assumes that MyObject.get_field("myField") will return the same field
|
32
84
|
# during the lifetime of a query
|
33
85
|
@fields_cache = Hash.new { |h, k| h[k] = {} }
|
34
|
-
|
35
|
-
|
36
|
-
def final_value
|
37
|
-
@response.final_value
|
86
|
+
# { Class => Boolean }
|
87
|
+
@lazy_cache = {}
|
38
88
|
end
|
39
89
|
|
40
90
|
def inspect
|
41
91
|
"#<#{self.class.name} response=#{@response.inspect}>"
|
42
92
|
end
|
43
93
|
|
94
|
+
def tap_or_each(obj_or_array)
|
95
|
+
if obj_or_array.is_a?(Array)
|
96
|
+
obj_or_array.each do |item|
|
97
|
+
yield(item, true)
|
98
|
+
end
|
99
|
+
else
|
100
|
+
yield(obj_or_array, false)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
44
104
|
# This _begins_ the execution. Some deferred work
|
45
105
|
# might be stored up in lazies.
|
46
106
|
# @return [void]
|
@@ -55,21 +115,42 @@ module GraphQL
|
|
55
115
|
|
56
116
|
if object_proxy.nil?
|
57
117
|
# Root .authorized? returned false.
|
58
|
-
|
118
|
+
@response = nil
|
59
119
|
else
|
60
|
-
resolve_with_directives(object_proxy, root_operation) do # execute query level directives
|
120
|
+
resolve_with_directives(object_proxy, root_operation.directives) do # execute query level directives
|
61
121
|
gathered_selections = gather_selections(object_proxy, root_type, root_operation.selections)
|
62
|
-
#
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
122
|
+
# This is kind of a hack -- `gathered_selections` is an Array if any of the selections
|
123
|
+
# require isolation during execution (because of runtime directives). In that case,
|
124
|
+
# make a new, isolated result hash for writing the result into. (That isolated response
|
125
|
+
# is eventually merged back into the main response)
|
126
|
+
#
|
127
|
+
# Otherwise, `gathered_selections` is a hash of selections which can be
|
128
|
+
# directly evaluated and the results can be written right into the main response hash.
|
129
|
+
tap_or_each(gathered_selections) do |selections, is_selection_array|
|
130
|
+
if is_selection_array
|
131
|
+
selection_response = GraphQLResultHash.new
|
132
|
+
final_response = @response
|
133
|
+
else
|
134
|
+
selection_response = @response
|
135
|
+
final_response = nil
|
136
|
+
end
|
137
|
+
|
138
|
+
@dataloader.append_job {
|
139
|
+
set_all_interpreter_context(query.root_value, nil, nil, path)
|
140
|
+
resolve_with_directives(object_proxy, selections.graphql_directives) do
|
141
|
+
evaluate_selections(
|
142
|
+
path,
|
143
|
+
context.scoped_context,
|
144
|
+
object_proxy,
|
145
|
+
root_type,
|
146
|
+
root_op_type == "mutation",
|
147
|
+
selections,
|
148
|
+
selection_response,
|
149
|
+
final_response,
|
150
|
+
)
|
151
|
+
end
|
152
|
+
}
|
153
|
+
end
|
73
154
|
end
|
74
155
|
end
|
75
156
|
delete_interpreter_context(:current_path)
|
@@ -79,15 +160,36 @@ module GraphQL
|
|
79
160
|
nil
|
80
161
|
end
|
81
162
|
|
82
|
-
|
163
|
+
# @return [void]
|
164
|
+
def deep_merge_selection_result(from_result, into_result)
|
165
|
+
from_result.each do |key, value|
|
166
|
+
if !into_result.key?(key)
|
167
|
+
into_result[key] = value
|
168
|
+
else
|
169
|
+
case value
|
170
|
+
when Hash
|
171
|
+
deep_merge_selection_result(value, into_result[key])
|
172
|
+
else
|
173
|
+
# We have to assume that, since this passed the `fields_will_merge` selection,
|
174
|
+
# that the old and new values are the same.
|
175
|
+
# There's no special handling of arrays because currently, there's no way to split the execution
|
176
|
+
# of a list over several concurrent flows.
|
177
|
+
into_result[key] = value
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
from_result.graphql_merged_into = into_result
|
182
|
+
nil
|
183
|
+
end
|
184
|
+
|
185
|
+
def gather_selections(owner_object, owner_type, selections, selections_to_run = nil, selections_by_name = GraphQLSelectionSet.new)
|
83
186
|
selections.each do |node|
|
84
187
|
# Skip gathering this if the directive says so
|
85
188
|
if !directives_include?(node, owner_object, owner_type)
|
86
189
|
next
|
87
190
|
end
|
88
191
|
|
89
|
-
|
90
|
-
when GraphQL::Language::Nodes::Field
|
192
|
+
if node.is_a?(GraphQL::Language::Nodes::Field)
|
91
193
|
response_key = node.alias || node.name
|
92
194
|
selections = selections_by_name[response_key]
|
93
195
|
# if there was already a selection of this field,
|
@@ -103,58 +205,83 @@ module GraphQL
|
|
103
205
|
# No selection was found for this field yet
|
104
206
|
selections_by_name[response_key] = node
|
105
207
|
end
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
208
|
+
else
|
209
|
+
# This is an InlineFragment or a FragmentSpread
|
210
|
+
if @runtime_directive_names.any? && node.directives.any? { |d| @runtime_directive_names.include?(d.name) }
|
211
|
+
next_selections = GraphQLSelectionSet.new
|
212
|
+
next_selections.graphql_directives = node.directives
|
213
|
+
if selections_to_run
|
214
|
+
selections_to_run << next_selections
|
215
|
+
else
|
216
|
+
selections_to_run = []
|
217
|
+
selections_to_run << selections_by_name
|
218
|
+
selections_to_run << next_selections
|
219
|
+
end
|
220
|
+
else
|
221
|
+
next_selections = selections_by_name
|
222
|
+
end
|
223
|
+
|
224
|
+
case node
|
225
|
+
when GraphQL::Language::Nodes::InlineFragment
|
226
|
+
if node.type
|
227
|
+
type_defn = schema.get_type(node.type.name)
|
228
|
+
|
229
|
+
# Faster than .map{}.include?()
|
230
|
+
query.warden.possible_types(type_defn).each do |t|
|
231
|
+
if t == owner_type
|
232
|
+
gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
|
233
|
+
break
|
234
|
+
end
|
235
|
+
end
|
236
|
+
else
|
237
|
+
# it's an untyped fragment, definitely continue
|
238
|
+
gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
|
239
|
+
end
|
240
|
+
when GraphQL::Language::Nodes::FragmentSpread
|
241
|
+
fragment_def = query.fragments[node.name]
|
242
|
+
type_defn = schema.get_type(fragment_def.type.name)
|
243
|
+
possible_types = query.warden.possible_types(type_defn)
|
244
|
+
possible_types.each do |t|
|
111
245
|
if t == owner_type
|
112
|
-
gather_selections(owner_object, owner_type,
|
246
|
+
gather_selections(owner_object, owner_type, fragment_def.selections, selections_to_run, next_selections)
|
113
247
|
break
|
114
248
|
end
|
115
249
|
end
|
116
250
|
else
|
117
|
-
|
118
|
-
gather_selections(owner_object, owner_type, node.selections, selections_by_name)
|
119
|
-
end
|
120
|
-
when GraphQL::Language::Nodes::FragmentSpread
|
121
|
-
fragment_def = query.fragments[node.name]
|
122
|
-
type_defn = schema.get_type(fragment_def.type.name)
|
123
|
-
possible_types = query.warden.possible_types(type_defn)
|
124
|
-
possible_types.each do |t|
|
125
|
-
if t == owner_type
|
126
|
-
gather_selections(owner_object, owner_type, fragment_def.selections, selections_by_name)
|
127
|
-
break
|
128
|
-
end
|
251
|
+
raise "Invariant: unexpected selection class: #{node.class}"
|
129
252
|
end
|
130
|
-
else
|
131
|
-
raise "Invariant: unexpected selection class: #{node.class}"
|
132
253
|
end
|
133
254
|
end
|
134
|
-
selections_by_name
|
255
|
+
selections_to_run || selections_by_name
|
135
256
|
end
|
136
257
|
|
137
258
|
NO_ARGS = {}.freeze
|
138
259
|
|
139
260
|
# @return [void]
|
140
|
-
def evaluate_selections(path, scoped_context, owner_object, owner_type, is_eager_selection, gathered_selections)
|
261
|
+
def evaluate_selections(path, scoped_context, owner_object, owner_type, is_eager_selection, gathered_selections, selections_result, target_result) # rubocop:disable Metrics/ParameterLists
|
141
262
|
set_all_interpreter_context(owner_object, nil, nil, path)
|
142
263
|
|
264
|
+
finished_jobs = 0
|
265
|
+
enqueued_jobs = gathered_selections.size
|
143
266
|
gathered_selections.each do |result_name, field_ast_nodes_or_ast_node|
|
144
267
|
@dataloader.append_job {
|
145
268
|
evaluate_selection(
|
146
|
-
path, result_name, field_ast_nodes_or_ast_node, scoped_context, owner_object, owner_type, is_eager_selection
|
269
|
+
path, result_name, field_ast_nodes_or_ast_node, scoped_context, owner_object, owner_type, is_eager_selection, selections_result
|
147
270
|
)
|
271
|
+
finished_jobs += 1
|
272
|
+
if target_result && finished_jobs == enqueued_jobs
|
273
|
+
deep_merge_selection_result(selections_result, target_result)
|
274
|
+
end
|
148
275
|
}
|
149
276
|
end
|
150
277
|
|
151
|
-
|
278
|
+
selections_result
|
152
279
|
end
|
153
280
|
|
154
281
|
attr_reader :progress_path
|
155
282
|
|
156
283
|
# @return [void]
|
157
|
-
def evaluate_selection(path, result_name, field_ast_nodes_or_ast_node, scoped_context, owner_object, owner_type, is_eager_field)
|
284
|
+
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
285
|
# As a performance optimization, the hash key will be a `Node` if
|
159
286
|
# there's only one selection of the field. But if there are multiple
|
160
287
|
# selections of the field, it will be an Array of nodes
|
@@ -188,7 +315,9 @@ module GraphQL
|
|
188
315
|
# This seems janky, but we need to know
|
189
316
|
# the field's return type at this path in order
|
190
317
|
# to propagate `null`
|
191
|
-
|
318
|
+
if return_type.non_null?
|
319
|
+
(selections_result.graphql_non_null_field_names ||= []).push(result_name)
|
320
|
+
end
|
192
321
|
# Set this before calling `run_with_directives`, so that the directive can have the latest path
|
193
322
|
set_all_interpreter_context(nil, field_defn, nil, next_path)
|
194
323
|
|
@@ -202,21 +331,21 @@ module GraphQL
|
|
202
331
|
total_args_count = field_defn.arguments.size
|
203
332
|
if total_args_count == 0
|
204
333
|
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)
|
334
|
+
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
335
|
else
|
207
336
|
# TODO remove all arguments(...) usages?
|
208
337
|
@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)
|
338
|
+
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
339
|
end
|
211
340
|
end
|
212
341
|
end
|
213
342
|
|
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
|
343
|
+
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
344
|
context.scoped_context = scoped_context
|
216
345
|
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|
|
346
|
+
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
347
|
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)
|
348
|
+
continue_value(next_path, resolved_arguments, owner_type, field_defn, return_type.non_null?, ast_node, result_name, selection_result)
|
220
349
|
next
|
221
350
|
end
|
222
351
|
|
@@ -268,12 +397,17 @@ module GraphQL
|
|
268
397
|
# Optimize for the case that field is selected only once
|
269
398
|
if field_ast_nodes.nil? || field_ast_nodes.size == 1
|
270
399
|
next_selections = ast_node.selections
|
400
|
+
directives = ast_node.directives
|
271
401
|
else
|
272
402
|
next_selections = []
|
273
|
-
|
403
|
+
directives = []
|
404
|
+
field_ast_nodes.each { |f|
|
405
|
+
next_selections.concat(f.selections)
|
406
|
+
directives.concat(f.directives)
|
407
|
+
}
|
274
408
|
end
|
275
409
|
|
276
|
-
field_result = resolve_with_directives(object,
|
410
|
+
field_result = resolve_with_directives(object, directives) do
|
277
411
|
# Actually call the field resolver and capture the result
|
278
412
|
app_result = begin
|
279
413
|
query.with_error_handling do
|
@@ -284,10 +418,10 @@ module GraphQL
|
|
284
418
|
rescue GraphQL::ExecutionError => err
|
285
419
|
err
|
286
420
|
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)
|
421
|
+
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|
|
422
|
+
continue_value = continue_value(next_path, inner_result, owner_type, field_defn, return_type.non_null?, ast_node, result_name, selection_result)
|
289
423
|
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)
|
424
|
+
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
425
|
end
|
292
426
|
end
|
293
427
|
end
|
@@ -304,43 +438,109 @@ module GraphQL
|
|
304
438
|
end
|
305
439
|
end
|
306
440
|
|
441
|
+
def dead_result?(selection_result)
|
442
|
+
r = selection_result
|
443
|
+
while r
|
444
|
+
if r.graphql_dead
|
445
|
+
return true
|
446
|
+
else
|
447
|
+
r = r.graphql_parent
|
448
|
+
end
|
449
|
+
end
|
450
|
+
false
|
451
|
+
end
|
452
|
+
|
453
|
+
def set_result(selection_result, result_name, value)
|
454
|
+
if !dead_result?(selection_result)
|
455
|
+
if value.nil? &&
|
456
|
+
( # there are two conditions under which `nil` is not allowed in the response:
|
457
|
+
(selection_result.graphql_non_null_list_items) || # this value would be written into a list that doesn't allow nils
|
458
|
+
((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
|
459
|
+
)
|
460
|
+
# This is an invalid nil that should be propagated
|
461
|
+
# One caller of this method passes a block,
|
462
|
+
# namely when application code returns a `nil` to GraphQL and it doesn't belong there.
|
463
|
+
# The other possibility for reaching here is when a field returns an ExecutionError, so we write
|
464
|
+
# `nil` to the response, not knowing whether it's an invalid `nil` or not.
|
465
|
+
# (And in that case, we don't have to call the schema's handler, since it's not a bug in the application.)
|
466
|
+
# TODO the code is trying to tell me something.
|
467
|
+
yield if block_given?
|
468
|
+
parent = selection_result.graphql_parent
|
469
|
+
name_in_parent = selection_result.graphql_result_name
|
470
|
+
if parent.nil? # This is a top-level result hash
|
471
|
+
@response = nil
|
472
|
+
else
|
473
|
+
set_result(parent, name_in_parent, nil)
|
474
|
+
# This is odd, but it's how it used to work. Even if `parent` _would_ accept
|
475
|
+
# a `nil`, it's marked dead. TODO: check the spec, is there a reason for this?
|
476
|
+
parent.graphql_dead = true
|
477
|
+
end
|
478
|
+
else
|
479
|
+
selection_result[result_name] = value
|
480
|
+
end
|
481
|
+
end
|
482
|
+
end
|
483
|
+
|
307
484
|
HALT = Object.new
|
308
|
-
def continue_value(path, value, parent_type, field, is_non_null, ast_node)
|
309
|
-
|
485
|
+
def continue_value(path, value, parent_type, field, is_non_null, ast_node, result_name, selection_result) # rubocop:disable Metrics/ParameterLists
|
486
|
+
case value
|
487
|
+
when nil
|
310
488
|
if is_non_null
|
311
|
-
|
312
|
-
|
489
|
+
set_result(selection_result, result_name, nil) do
|
490
|
+
# This block is called if `result_name` is not dead. (Maybe a previous invalid nil caused it be marked dead.)
|
491
|
+
err = parent_type::InvalidNullError.new(parent_type, field, value)
|
492
|
+
schema.type_error(err, context)
|
493
|
+
end
|
313
494
|
else
|
314
|
-
|
495
|
+
set_result(selection_result, result_name, nil)
|
315
496
|
end
|
316
497
|
HALT
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
498
|
+
when GraphQL::Error
|
499
|
+
# Handle these cases inside a single `when`
|
500
|
+
# to avoid the overhead of checking three different classes
|
501
|
+
# every time.
|
502
|
+
if value.is_a?(GraphQL::ExecutionError)
|
503
|
+
if !dead_result?(selection_result)
|
504
|
+
value.path ||= path
|
505
|
+
value.ast_node ||= ast_node
|
506
|
+
context.errors << value
|
507
|
+
set_result(selection_result, result_name, nil)
|
508
|
+
end
|
509
|
+
HALT
|
510
|
+
elsif value.is_a?(GraphQL::UnauthorizedError)
|
511
|
+
# this hook might raise & crash, or it might return
|
512
|
+
# a replacement value
|
513
|
+
next_value = begin
|
514
|
+
schema.unauthorized_object(value)
|
515
|
+
rescue GraphQL::ExecutionError => err
|
516
|
+
err
|
517
|
+
end
|
518
|
+
continue_value(path, next_value, parent_type, field, is_non_null, ast_node, result_name, selection_result)
|
519
|
+
elsif GraphQL::Execution::Execute::SKIP == value
|
520
|
+
HALT
|
521
|
+
else
|
522
|
+
# What could this actually _be_? Anyhow,
|
523
|
+
# preserve the default behavior of doing nothing with it.
|
524
|
+
value
|
326
525
|
end
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
526
|
+
when Array
|
527
|
+
# It's an array full of execution errors; add them all.
|
528
|
+
if value.any? && value.all? { |v| v.is_a?(GraphQL::ExecutionError) }
|
529
|
+
if !dead_result?(selection_result)
|
530
|
+
value.each_with_index do |error, index|
|
531
|
+
error.ast_node ||= ast_node
|
532
|
+
error.path ||= path + (field.type.list? ? [index] : [])
|
533
|
+
context.errors << error
|
534
|
+
end
|
535
|
+
set_result(selection_result, result_name, nil)
|
536
|
+
end
|
537
|
+
HALT
|
538
|
+
else
|
539
|
+
value
|
336
540
|
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)
|
541
|
+
when GraphQL::Execution::Interpreter::RawValue
|
342
542
|
# Write raw value directly to the response without resolving nested objects
|
343
|
-
|
543
|
+
set_result(selection_result, result_name, value.resolve)
|
344
544
|
HALT
|
345
545
|
else
|
346
546
|
value
|
@@ -355,17 +555,22 @@ module GraphQL
|
|
355
555
|
# Location information from `path` and `ast_node`.
|
356
556
|
#
|
357
557
|
# @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
|
558
|
+
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
|
559
|
+
if current_type.non_null?
|
560
|
+
current_type = current_type.of_type
|
561
|
+
is_non_null = true
|
562
|
+
end
|
563
|
+
|
359
564
|
case current_type.kind.name
|
360
565
|
when "SCALAR", "ENUM"
|
361
566
|
r = current_type.coerce_result(value, context)
|
362
|
-
|
567
|
+
set_result(selection_result, result_name, r)
|
363
568
|
r
|
364
569
|
when "UNION", "INTERFACE"
|
365
570
|
resolved_type_or_lazy, resolved_value = resolve_type(current_type, value, path)
|
366
571
|
resolved_value ||= value
|
367
572
|
|
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|
|
573
|
+
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
574
|
possible_types = query.possible_types(current_type)
|
370
575
|
|
371
576
|
if !possible_types.include?(resolved_type)
|
@@ -373,10 +578,10 @@ module GraphQL
|
|
373
578
|
err_class = current_type::UnresolvedTypeError
|
374
579
|
type_error = err_class.new(resolved_value, field, parent_type, resolved_type, possible_types)
|
375
580
|
schema.type_error(type_error, context)
|
376
|
-
|
581
|
+
set_result(selection_result, result_name, nil)
|
377
582
|
nil
|
378
583
|
else
|
379
|
-
continue_field(path, resolved_value, owner_type, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments)
|
584
|
+
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
585
|
end
|
381
586
|
end
|
382
587
|
when "OBJECT"
|
@@ -385,34 +590,71 @@ module GraphQL
|
|
385
590
|
rescue GraphQL::ExecutionError => err
|
386
591
|
err
|
387
592
|
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)
|
593
|
+
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|
|
594
|
+
continue_value = continue_value(path, inner_object, owner_type, field, is_non_null, ast_node, result_name, selection_result)
|
390
595
|
if HALT != continue_value
|
391
|
-
response_hash =
|
392
|
-
|
596
|
+
response_hash = GraphQLResultHash.new
|
597
|
+
response_hash.graphql_parent = selection_result
|
598
|
+
response_hash.graphql_result_name = result_name
|
599
|
+
set_result(selection_result, result_name, response_hash)
|
393
600
|
gathered_selections = gather_selections(continue_value, current_type, next_selections)
|
394
|
-
|
395
|
-
|
601
|
+
# There are two possibilities for `gathered_selections`:
|
602
|
+
# 1. All selections of this object should be evaluated together (there are no runtime directives modifying execution).
|
603
|
+
# This case is handled below, and the result can be written right into the main `response_hash` above.
|
604
|
+
# In this case, `gathered_selections` is a hash of selections.
|
605
|
+
# 2. Some selections of this object have runtime directives that may or may not modify execution.
|
606
|
+
# That part of the selection is evaluated in an isolated way, writing into a sub-response object which is
|
607
|
+
# eventually merged into the final response. In this case, `gathered_selections` is an array of things to run in isolation.
|
608
|
+
# (Technically, it's possible that one of those entries _doesn't_ require isolation.)
|
609
|
+
tap_or_each(gathered_selections) do |selections, is_selection_array|
|
610
|
+
if is_selection_array
|
611
|
+
this_result = GraphQLResultHash.new
|
612
|
+
this_result.graphql_parent = selection_result
|
613
|
+
this_result.graphql_result_name = result_name
|
614
|
+
final_result = response_hash
|
615
|
+
else
|
616
|
+
this_result = response_hash
|
617
|
+
final_result = nil
|
618
|
+
end
|
619
|
+
set_all_interpreter_context(continue_value, nil, nil, path) # reset this mutable state
|
620
|
+
resolve_with_directives(continue_value, selections.graphql_directives) do
|
621
|
+
evaluate_selections(
|
622
|
+
path,
|
623
|
+
context.scoped_context,
|
624
|
+
continue_value,
|
625
|
+
current_type,
|
626
|
+
false,
|
627
|
+
selections,
|
628
|
+
this_result,
|
629
|
+
final_result,
|
630
|
+
)
|
631
|
+
this_result
|
632
|
+
end
|
633
|
+
end
|
396
634
|
end
|
397
635
|
end
|
398
636
|
when "LIST"
|
399
|
-
response_list = []
|
400
|
-
write_in_response(path, response_list)
|
401
637
|
inner_type = current_type.of_type
|
638
|
+
response_list = GraphQLResultArray.new
|
639
|
+
response_list.graphql_non_null_list_items = inner_type.non_null?
|
640
|
+
response_list.graphql_parent = selection_result
|
641
|
+
response_list.graphql_result_name = result_name
|
642
|
+
set_result(selection_result, result_name, response_list)
|
643
|
+
|
402
644
|
idx = 0
|
403
645
|
scoped_context = context.scoped_context
|
404
646
|
begin
|
405
647
|
value.each do |inner_value|
|
406
648
|
next_path = path.dup
|
407
649
|
next_path << idx
|
650
|
+
this_idx = idx
|
408
651
|
next_path.freeze
|
409
652
|
idx += 1
|
410
|
-
set_type_at_path(next_path, inner_type)
|
411
653
|
# 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)
|
654
|
+
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|
|
655
|
+
continue_value = continue_value(next_path, inner_inner_value, owner_type, field, inner_type.non_null?, ast_node, this_idx, response_list)
|
414
656
|
if HALT != continue_value
|
415
|
-
continue_field(next_path, continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments)
|
657
|
+
continue_field(next_path, continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments, this_idx, response_list)
|
416
658
|
end
|
417
659
|
end
|
418
660
|
end
|
@@ -428,23 +670,18 @@ module GraphQL
|
|
428
670
|
end
|
429
671
|
|
430
672
|
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
673
|
else
|
437
674
|
raise "Invariant: Unhandled type kind #{current_type.kind} (#{current_type})"
|
438
675
|
end
|
439
676
|
end
|
440
677
|
|
441
|
-
def resolve_with_directives(object,
|
442
|
-
return yield if
|
443
|
-
run_directive(object,
|
678
|
+
def resolve_with_directives(object, directives, &block)
|
679
|
+
return yield if directives.nil? || directives.empty?
|
680
|
+
run_directive(object, directives, 0, &block)
|
444
681
|
end
|
445
682
|
|
446
|
-
def run_directive(object,
|
447
|
-
dir_node =
|
683
|
+
def run_directive(object, directives, idx, &block)
|
684
|
+
dir_node = directives[idx]
|
448
685
|
if !dir_node
|
449
686
|
yield
|
450
687
|
else
|
@@ -452,9 +689,9 @@ module GraphQL
|
|
452
689
|
if !dir_defn.is_a?(Class)
|
453
690
|
dir_defn = dir_defn.type_class || raise("Only class-based directives are supported (not `@#{dir_node.name}`)")
|
454
691
|
end
|
455
|
-
dir_args = arguments(nil, dir_defn, dir_node)
|
692
|
+
dir_args = arguments(nil, dir_defn, dir_node)
|
456
693
|
dir_defn.resolve(object, dir_args, context) do
|
457
|
-
run_directive(object,
|
694
|
+
run_directive(object, directives, idx + 1, &block)
|
458
695
|
end
|
459
696
|
end
|
460
697
|
end
|
@@ -463,7 +700,7 @@ module GraphQL
|
|
463
700
|
def directives_include?(node, graphql_object, parent_type)
|
464
701
|
node.directives.each do |dir_node|
|
465
702
|
dir_defn = schema.directives.fetch(dir_node.name).type_class || raise("Only class-based directives are supported (not #{dir_node.name.inspect})")
|
466
|
-
args = arguments(graphql_object, dir_defn, dir_node)
|
703
|
+
args = arguments(graphql_object, dir_defn, dir_node)
|
467
704
|
if !dir_defn.include?(graphql_object, args, context)
|
468
705
|
return false
|
469
706
|
end
|
@@ -492,9 +729,8 @@ module GraphQL
|
|
492
729
|
# @param eager [Boolean] Set to `true` for mutation root fields only
|
493
730
|
# @param trace [Boolean] If `false`, don't wrap this with field tracing
|
494
731
|
# @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)
|
732
|
+
def after_lazy(lazy_obj, owner:, field:, path:, scoped_context:, owner_object:, arguments:, ast_node:, result:, result_name:, eager: false, trace: true, &block)
|
733
|
+
if lazy?(lazy_obj)
|
498
734
|
lazy = GraphQL::Execution::Lazy.new(path: path, field: field) do
|
499
735
|
set_all_interpreter_context(owner_object, field, arguments, path)
|
500
736
|
context.scoped_context = scoped_context
|
@@ -513,16 +749,17 @@ module GraphQL
|
|
513
749
|
rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => err
|
514
750
|
err
|
515
751
|
end
|
516
|
-
|
752
|
+
yield(inner_obj)
|
517
753
|
end
|
518
754
|
|
519
755
|
if eager
|
520
756
|
lazy.value
|
521
757
|
else
|
522
|
-
|
758
|
+
set_result(result, result_name, lazy)
|
523
759
|
lazy
|
524
760
|
end
|
525
761
|
else
|
762
|
+
set_all_interpreter_context(owner_object, field, arguments, path)
|
526
763
|
yield(lazy_obj)
|
527
764
|
end
|
528
765
|
end
|
@@ -536,85 +773,6 @@ module GraphQL
|
|
536
773
|
end
|
537
774
|
end
|
538
775
|
|
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
776
|
# Set this pair in the Query context, but also in the interpeter namespace,
|
619
777
|
# for compatibility.
|
620
778
|
def set_interpreter_context(key, value)
|
@@ -633,7 +791,7 @@ module GraphQL
|
|
633
791
|
query.resolve_type(type, value)
|
634
792
|
end
|
635
793
|
|
636
|
-
if
|
794
|
+
if lazy?(resolved_type)
|
637
795
|
GraphQL::Execution::Lazy.new do
|
638
796
|
query.trace("resolve_type_lazy", trace_payload) do
|
639
797
|
schema.sync_lazy(resolved_type)
|
@@ -647,6 +805,12 @@ module GraphQL
|
|
647
805
|
def authorized_new(type, value, context)
|
648
806
|
type.authorized_new(value, context)
|
649
807
|
end
|
808
|
+
|
809
|
+
def lazy?(object)
|
810
|
+
@lazy_cache.fetch(object.class) {
|
811
|
+
@lazy_cache[object.class] = @schema.lazy?(object)
|
812
|
+
}
|
813
|
+
end
|
650
814
|
end
|
651
815
|
end
|
652
816
|
end
|