graphql 2.0.14 → 2.0.32
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/generators/graphql/mutation_delete_generator.rb +1 -1
- data/lib/generators/graphql/mutation_update_generator.rb +1 -1
- data/lib/generators/graphql/relay.rb +18 -1
- data/lib/graphql/analysis/ast/visitor.rb +42 -35
- data/lib/graphql/analysis/ast.rb +2 -2
- data/lib/graphql/backtrace/table.rb +2 -2
- data/lib/graphql/backtrace/trace.rb +96 -0
- data/lib/graphql/backtrace/tracer.rb +1 -1
- data/lib/graphql/backtrace.rb +2 -1
- data/lib/graphql/dataloader/source.rb +69 -45
- data/lib/graphql/dataloader.rb +8 -5
- data/lib/graphql/execution/interpreter/arguments.rb +1 -1
- data/lib/graphql/execution/interpreter/arguments_cache.rb +33 -33
- data/lib/graphql/execution/interpreter/resolve.rb +19 -0
- data/lib/graphql/execution/interpreter/runtime.rb +355 -268
- data/lib/graphql/execution/interpreter.rb +19 -15
- data/lib/graphql/execution/lazy.rb +6 -12
- data/lib/graphql/execution/lookahead.rb +16 -5
- data/lib/graphql/execution/multiplex.rb +2 -1
- data/lib/graphql/filter.rb +8 -2
- data/lib/graphql/introspection/directive_type.rb +2 -2
- data/lib/graphql/introspection/entry_points.rb +1 -1
- data/lib/graphql/introspection/field_type.rb +1 -1
- data/lib/graphql/introspection/schema_type.rb +2 -2
- data/lib/graphql/introspection/type_type.rb +5 -5
- data/lib/graphql/introspection.rb +1 -1
- data/lib/graphql/language/document_from_schema_definition.rb +58 -35
- data/lib/graphql/language/lexer.rb +248 -1505
- data/lib/graphql/language/nodes.rb +69 -40
- data/lib/graphql/language/parser.rb +775 -742
- data/lib/graphql/language/parser.y +44 -38
- data/lib/graphql/language/printer.rb +48 -25
- data/lib/graphql/language/visitor.rb +192 -81
- data/lib/graphql/pagination/active_record_relation_connection.rb +0 -8
- data/lib/graphql/pagination/connection.rb +5 -5
- data/lib/graphql/query/context.rb +93 -27
- data/lib/graphql/query/null_context.rb +8 -18
- data/lib/graphql/query/validation_pipeline.rb +2 -1
- data/lib/graphql/query.rb +55 -13
- data/lib/graphql/rake_task.rb +28 -1
- data/lib/graphql/schema/addition.rb +38 -12
- data/lib/graphql/schema/always_visible.rb +10 -0
- data/lib/graphql/schema/argument.rb +15 -23
- data/lib/graphql/schema/build_from_definition.rb +54 -25
- data/lib/graphql/schema/directive/transform.rb +1 -1
- data/lib/graphql/schema/directive.rb +12 -2
- data/lib/graphql/schema/enum.rb +24 -17
- data/lib/graphql/schema/enum_value.rb +3 -4
- data/lib/graphql/schema/field/connection_extension.rb +1 -1
- data/lib/graphql/schema/field.rb +95 -73
- data/lib/graphql/schema/field_extension.rb +1 -4
- data/lib/graphql/schema/find_inherited_value.rb +2 -7
- data/lib/graphql/schema/input_object.rb +9 -7
- data/lib/graphql/schema/interface.rb +5 -11
- data/lib/graphql/schema/introspection_system.rb +1 -1
- data/lib/graphql/schema/late_bound_type.rb +2 -0
- data/lib/graphql/schema/member/base_dsl_methods.rb +17 -14
- data/lib/graphql/schema/member/build_type.rb +11 -3
- data/lib/graphql/schema/member/has_arguments.rb +114 -65
- data/lib/graphql/schema/member/has_ast_node.rb +12 -0
- data/lib/graphql/schema/member/has_deprecation_reason.rb +3 -4
- data/lib/graphql/schema/member/has_directives.rb +81 -61
- data/lib/graphql/schema/member/has_fields.rb +95 -38
- data/lib/graphql/schema/member/has_interfaces.rb +49 -8
- data/lib/graphql/schema/member/has_validators.rb +32 -6
- data/lib/graphql/schema/member/relay_shortcuts.rb +19 -0
- data/lib/graphql/schema/member/type_system_helpers.rb +17 -0
- data/lib/graphql/schema/object.rb +8 -5
- data/lib/graphql/schema/printer.rb +3 -1
- data/lib/graphql/schema/relay_classic_mutation.rb +1 -1
- data/lib/graphql/schema/resolver/has_payload_type.rb +9 -9
- data/lib/graphql/schema/resolver.rb +16 -14
- data/lib/graphql/schema/timeout.rb +25 -29
- data/lib/graphql/schema/type_membership.rb +3 -0
- data/lib/graphql/schema/union.rb +10 -1
- data/lib/graphql/schema/validator.rb +2 -2
- data/lib/graphql/schema/warden.rb +64 -7
- data/lib/graphql/schema.rb +171 -28
- data/lib/graphql/static_validation/definition_dependencies.rb +7 -1
- data/lib/graphql/static_validation/literal_validator.rb +15 -1
- data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +12 -4
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +2 -2
- data/lib/graphql/static_validation/validator.rb +1 -1
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +7 -1
- data/lib/graphql/subscriptions/event.rb +2 -7
- data/lib/graphql/subscriptions.rb +5 -0
- data/lib/graphql/tracing/active_support_notifications_trace.rb +16 -0
- data/lib/graphql/tracing/appoptics_trace.rb +255 -0
- data/lib/graphql/tracing/appsignal_trace.rb +81 -0
- data/lib/graphql/tracing/data_dog_trace.rb +187 -0
- data/lib/graphql/tracing/data_dog_tracing.rb +7 -21
- data/lib/graphql/tracing/legacy_trace.rb +69 -0
- data/lib/graphql/tracing/new_relic_trace.rb +75 -0
- data/lib/graphql/tracing/notifications_trace.rb +49 -0
- data/lib/graphql/tracing/platform_trace.rb +123 -0
- data/lib/graphql/tracing/platform_tracing.rb +15 -3
- data/lib/graphql/tracing/prometheus_trace.rb +93 -0
- data/lib/graphql/tracing/prometheus_tracing/graphql_collector.rb +1 -1
- data/lib/graphql/tracing/prometheus_tracing.rb +3 -3
- data/lib/graphql/tracing/scout_trace.rb +75 -0
- data/lib/graphql/tracing/statsd_trace.rb +60 -0
- data/lib/graphql/tracing/trace.rb +75 -0
- data/lib/graphql/tracing.rb +17 -39
- data/lib/graphql/type_kinds.rb +6 -3
- data/lib/graphql/types/relay/base_connection.rb +1 -1
- data/lib/graphql/types/relay/connection_behaviors.rb +28 -6
- data/lib/graphql/types/relay/edge_behaviors.rb +16 -5
- data/lib/graphql/types/relay/node_behaviors.rb +8 -2
- data/lib/graphql/types/relay/page_info_behaviors.rb +7 -2
- data/lib/graphql/types/relay.rb +0 -1
- data/lib/graphql/types/string.rb +1 -1
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +16 -9
- data/readme.md +1 -1
- metadata +66 -29
- data/lib/graphql/language/lexer.rl +0 -280
- data/lib/graphql/types/relay/default_relay.rb +0 -21
|
@@ -8,35 +8,49 @@ module GraphQL
|
|
|
8
8
|
#
|
|
9
9
|
# @api private
|
|
10
10
|
class Runtime
|
|
11
|
+
class CurrentState
|
|
12
|
+
def initialize
|
|
13
|
+
@current_object = nil
|
|
14
|
+
@current_field = nil
|
|
15
|
+
@current_arguments = nil
|
|
16
|
+
@current_result_name = nil
|
|
17
|
+
@current_result = nil
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
attr_accessor :current_result, :current_result_name,
|
|
21
|
+
:current_arguments, :current_field, :current_object
|
|
22
|
+
end
|
|
11
23
|
|
|
12
24
|
module GraphQLResult
|
|
13
|
-
def initialize(result_name, parent_result)
|
|
25
|
+
def initialize(result_name, parent_result, is_non_null_in_parent)
|
|
14
26
|
@graphql_parent = parent_result
|
|
15
27
|
if parent_result && parent_result.graphql_dead
|
|
16
28
|
@graphql_dead = true
|
|
17
29
|
end
|
|
18
30
|
@graphql_result_name = result_name
|
|
31
|
+
@graphql_is_non_null_in_parent = is_non_null_in_parent
|
|
19
32
|
# Jump through some hoops to avoid creating this duplicate storage if at all possible.
|
|
20
33
|
@graphql_metadata = nil
|
|
21
34
|
end
|
|
22
35
|
|
|
23
|
-
|
|
24
|
-
|
|
36
|
+
def path
|
|
37
|
+
@path ||= build_path([])
|
|
38
|
+
end
|
|
25
39
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
40
|
+
def build_path(path_array)
|
|
41
|
+
graphql_result_name && path_array.unshift(graphql_result_name)
|
|
42
|
+
@graphql_parent ? @graphql_parent.build_path(path_array) : path_array
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
attr_accessor :graphql_dead
|
|
46
|
+
attr_reader :graphql_parent, :graphql_result_name, :graphql_is_non_null_in_parent
|
|
33
47
|
|
|
34
48
|
# @return [Hash] Plain-Ruby result data (`@graphql_metadata` contains Result wrapper objects)
|
|
35
49
|
attr_accessor :graphql_result_data
|
|
36
50
|
end
|
|
37
51
|
|
|
38
52
|
class GraphQLResultHash
|
|
39
|
-
def initialize(_result_name, _parent_result)
|
|
53
|
+
def initialize(_result_name, _parent_result, _is_non_null_in_parent)
|
|
40
54
|
super
|
|
41
55
|
@graphql_result_data = {}
|
|
42
56
|
end
|
|
@@ -45,7 +59,7 @@ module GraphQL
|
|
|
45
59
|
|
|
46
60
|
attr_accessor :graphql_merged_into
|
|
47
61
|
|
|
48
|
-
def
|
|
62
|
+
def set_leaf(key, value)
|
|
49
63
|
# This is a hack.
|
|
50
64
|
# Basically, this object is merged into the root-level result at some point.
|
|
51
65
|
# But the problem is, some lazies are created whose closures retain reference to _this_
|
|
@@ -55,20 +69,24 @@ module GraphQL
|
|
|
55
69
|
# In order to return a proper partial result (eg, for a directive), we have to update this object, too.
|
|
56
70
|
# Yowza.
|
|
57
71
|
if (t = @graphql_merged_into)
|
|
58
|
-
t
|
|
72
|
+
t.set_leaf(key, value)
|
|
59
73
|
end
|
|
60
74
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
@graphql_result_data[key] = value
|
|
68
|
-
# keep this up-to-date if it's been initialized
|
|
69
|
-
@graphql_metadata && @graphql_metadata[key] = value
|
|
70
|
-
end
|
|
75
|
+
@graphql_result_data[key] = value
|
|
76
|
+
# keep this up-to-date if it's been initialized
|
|
77
|
+
@graphql_metadata && @graphql_metadata[key] = value
|
|
78
|
+
|
|
79
|
+
value
|
|
80
|
+
end
|
|
71
81
|
|
|
82
|
+
def set_child_result(key, value)
|
|
83
|
+
if (t = @graphql_merged_into)
|
|
84
|
+
t.set_child_result(key, value)
|
|
85
|
+
end
|
|
86
|
+
@graphql_result_data[key] = value.graphql_result_data
|
|
87
|
+
# If we encounter some part of this response that requires metadata tracking,
|
|
88
|
+
# then create the metadata hash if necessary. It will be kept up-to-date after this.
|
|
89
|
+
(@graphql_metadata ||= @graphql_result_data.dup)[key] = value
|
|
72
90
|
value
|
|
73
91
|
end
|
|
74
92
|
|
|
@@ -92,12 +110,35 @@ module GraphQL
|
|
|
92
110
|
def [](k)
|
|
93
111
|
(@graphql_metadata || @graphql_result_data)[k]
|
|
94
112
|
end
|
|
113
|
+
|
|
114
|
+
def merge_into(into_result)
|
|
115
|
+
self.each do |key, value|
|
|
116
|
+
case value
|
|
117
|
+
when GraphQLResultHash
|
|
118
|
+
next_into = into_result[key]
|
|
119
|
+
if next_into
|
|
120
|
+
value.merge_into(next_into)
|
|
121
|
+
else
|
|
122
|
+
into_result.set_child_result(key, value)
|
|
123
|
+
end
|
|
124
|
+
when GraphQLResultArray
|
|
125
|
+
# There's no special handling of arrays because currently, there's no way to split the execution
|
|
126
|
+
# of a list over several concurrent flows.
|
|
127
|
+
into_result.set_child_result(key, value)
|
|
128
|
+
else
|
|
129
|
+
# We have to assume that, since this passed the `fields_will_merge` selection,
|
|
130
|
+
# that the old and new values are the same.
|
|
131
|
+
into_result.set_leaf(key, value)
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
@graphql_merged_into = into_result
|
|
135
|
+
end
|
|
95
136
|
end
|
|
96
137
|
|
|
97
138
|
class GraphQLResultArray
|
|
98
139
|
include GraphQLResult
|
|
99
140
|
|
|
100
|
-
def initialize(_result_name, _parent_result)
|
|
141
|
+
def initialize(_result_name, _parent_result, _is_non_null_in_parent)
|
|
101
142
|
super
|
|
102
143
|
@graphql_result_data = []
|
|
103
144
|
end
|
|
@@ -114,19 +155,25 @@ module GraphQL
|
|
|
114
155
|
@graphql_result_data.delete_at(delete_at_index)
|
|
115
156
|
end
|
|
116
157
|
|
|
117
|
-
def
|
|
158
|
+
def set_leaf(idx, value)
|
|
118
159
|
if @skip_indices
|
|
119
160
|
offset_by = @skip_indices.count { |skipped_idx| skipped_idx < idx }
|
|
120
161
|
idx -= offset_by
|
|
121
162
|
end
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
@graphql_result_data[idx] = value
|
|
127
|
-
@graphql_metadata && @graphql_metadata[idx] = value
|
|
128
|
-
end
|
|
163
|
+
@graphql_result_data[idx] = value
|
|
164
|
+
@graphql_metadata && @graphql_metadata[idx] = value
|
|
165
|
+
value
|
|
166
|
+
end
|
|
129
167
|
|
|
168
|
+
def set_child_result(idx, value)
|
|
169
|
+
if @skip_indices
|
|
170
|
+
offset_by = @skip_indices.count { |skipped_idx| skipped_idx < idx }
|
|
171
|
+
idx -= offset_by
|
|
172
|
+
end
|
|
173
|
+
@graphql_result_data[idx] = value.graphql_result_data
|
|
174
|
+
# If we encounter some part of this response that requires metadata tracking,
|
|
175
|
+
# then create the metadata hash if necessary. It will be kept up-to-date after this.
|
|
176
|
+
(@graphql_metadata ||= @graphql_result_data.dup)[idx] = value
|
|
130
177
|
value
|
|
131
178
|
end
|
|
132
179
|
|
|
@@ -135,10 +182,6 @@ module GraphQL
|
|
|
135
182
|
end
|
|
136
183
|
end
|
|
137
184
|
|
|
138
|
-
class GraphQLSelectionSet < Hash
|
|
139
|
-
attr_accessor :graphql_directives
|
|
140
|
-
end
|
|
141
|
-
|
|
142
185
|
# @return [GraphQL::Query]
|
|
143
186
|
attr_reader :query
|
|
144
187
|
|
|
@@ -148,14 +191,14 @@ module GraphQL
|
|
|
148
191
|
# @return [GraphQL::Query::Context]
|
|
149
192
|
attr_reader :context
|
|
150
193
|
|
|
151
|
-
def initialize(query:)
|
|
194
|
+
def initialize(query:, lazies_at_depth:)
|
|
152
195
|
@query = query
|
|
196
|
+
@current_trace = query.current_trace
|
|
153
197
|
@dataloader = query.multiplex.dataloader
|
|
198
|
+
@lazies_at_depth = lazies_at_depth
|
|
154
199
|
@schema = query.schema
|
|
155
200
|
@context = query.context
|
|
156
|
-
@
|
|
157
|
-
@interpreter_context = @context.namespace(:interpreter)
|
|
158
|
-
@response = GraphQLResultHash.new(nil, nil)
|
|
201
|
+
@response = GraphQLResultHash.new(nil, nil, false)
|
|
159
202
|
# Identify runtime directives by checking which of this schema's directives have overridden `def self.resolve`
|
|
160
203
|
@runtime_directive_names = []
|
|
161
204
|
noop_resolve_owner = GraphQL::Schema::Directive.singleton_class
|
|
@@ -169,8 +212,11 @@ module GraphQL
|
|
|
169
212
|
# Which assumes that MyObject.get_field("myField") will return the same field
|
|
170
213
|
# during the lifetime of a query
|
|
171
214
|
@fields_cache = Hash.new { |h, k| h[k] = {} }
|
|
215
|
+
# this can by by-identity since owners are the same object, but not the sub-hash, which uses strings.
|
|
216
|
+
@fields_cache.compare_by_identity
|
|
172
217
|
# { Class => Boolean }
|
|
173
218
|
@lazy_cache = {}
|
|
219
|
+
@lazy_cache.compare_by_identity
|
|
174
220
|
end
|
|
175
221
|
|
|
176
222
|
def final_result
|
|
@@ -198,17 +244,18 @@ module GraphQL
|
|
|
198
244
|
root_operation = query.selected_operation
|
|
199
245
|
root_op_type = root_operation.operation_type || "query"
|
|
200
246
|
root_type = schema.root_type_for_operation(root_op_type)
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
247
|
+
st = get_current_runtime_state
|
|
248
|
+
st.current_object = query.root_value
|
|
249
|
+
st.current_result = @response
|
|
250
|
+
runtime_object = root_type.wrap(query.root_value, context)
|
|
251
|
+
runtime_object = schema.sync_lazy(runtime_object)
|
|
205
252
|
|
|
206
|
-
if
|
|
253
|
+
if runtime_object.nil?
|
|
207
254
|
# Root .authorized? returned false.
|
|
208
255
|
@response = nil
|
|
209
256
|
else
|
|
210
|
-
call_method_on_directives(:resolve,
|
|
211
|
-
gathered_selections = gather_selections(
|
|
257
|
+
call_method_on_directives(:resolve, runtime_object, root_operation.directives) do # execute query level directives
|
|
258
|
+
gathered_selections = gather_selections(runtime_object, root_type, root_operation.selections)
|
|
212
259
|
# This is kind of a hack -- `gathered_selections` is an Array if any of the selections
|
|
213
260
|
# require isolation during execution (because of runtime directives). In that case,
|
|
214
261
|
# make a new, isolated result hash for writing the result into. (That isolated response
|
|
@@ -218,7 +265,7 @@ module GraphQL
|
|
|
218
265
|
# directly evaluated and the results can be written right into the main response hash.
|
|
219
266
|
tap_or_each(gathered_selections) do |selections, is_selection_array|
|
|
220
267
|
if is_selection_array
|
|
221
|
-
selection_response = GraphQLResultHash.new(nil, nil)
|
|
268
|
+
selection_response = GraphQLResultHash.new(nil, nil, false)
|
|
222
269
|
final_response = @response
|
|
223
270
|
else
|
|
224
271
|
selection_response = @response
|
|
@@ -226,11 +273,16 @@ module GraphQL
|
|
|
226
273
|
end
|
|
227
274
|
|
|
228
275
|
@dataloader.append_job {
|
|
229
|
-
|
|
230
|
-
|
|
276
|
+
st = get_current_runtime_state
|
|
277
|
+
st.current_object = query.root_value
|
|
278
|
+
st.current_result = selection_response
|
|
279
|
+
# This is a less-frequent case; use a fast check since it's often not there.
|
|
280
|
+
if (directives = selections[:graphql_directives])
|
|
281
|
+
selections.delete(:graphql_directives)
|
|
282
|
+
end
|
|
283
|
+
call_method_on_directives(:resolve, runtime_object, directives) do
|
|
231
284
|
evaluate_selections(
|
|
232
|
-
|
|
233
|
-
object_proxy,
|
|
285
|
+
runtime_object,
|
|
234
286
|
root_type,
|
|
235
287
|
root_op_type == "mutation",
|
|
236
288
|
selections,
|
|
@@ -243,36 +295,11 @@ module GraphQL
|
|
|
243
295
|
end
|
|
244
296
|
end
|
|
245
297
|
end
|
|
246
|
-
|
|
247
|
-
delete_interpreter_context(:current_field)
|
|
248
|
-
delete_interpreter_context(:current_object)
|
|
249
|
-
delete_interpreter_context(:current_arguments)
|
|
298
|
+
delete_all_interpreter_context
|
|
250
299
|
nil
|
|
251
300
|
end
|
|
252
301
|
|
|
253
|
-
|
|
254
|
-
def deep_merge_selection_result(from_result, into_result)
|
|
255
|
-
from_result.each do |key, value|
|
|
256
|
-
if !into_result.key?(key)
|
|
257
|
-
into_result[key] = value
|
|
258
|
-
else
|
|
259
|
-
case value
|
|
260
|
-
when GraphQLResultHash
|
|
261
|
-
deep_merge_selection_result(value, into_result[key])
|
|
262
|
-
else
|
|
263
|
-
# We have to assume that, since this passed the `fields_will_merge` selection,
|
|
264
|
-
# that the old and new values are the same.
|
|
265
|
-
# There's no special handling of arrays because currently, there's no way to split the execution
|
|
266
|
-
# of a list over several concurrent flows.
|
|
267
|
-
into_result[key] = value
|
|
268
|
-
end
|
|
269
|
-
end
|
|
270
|
-
end
|
|
271
|
-
from_result.graphql_merged_into = into_result
|
|
272
|
-
nil
|
|
273
|
-
end
|
|
274
|
-
|
|
275
|
-
def gather_selections(owner_object, owner_type, selections, selections_to_run = nil, selections_by_name = GraphQLSelectionSet.new)
|
|
302
|
+
def gather_selections(owner_object, owner_type, selections, selections_to_run = nil, selections_by_name = {})
|
|
276
303
|
selections.each do |node|
|
|
277
304
|
# Skip gathering this if the directive says so
|
|
278
305
|
if !directives_include?(node, owner_object, owner_type)
|
|
@@ -298,8 +325,8 @@ module GraphQL
|
|
|
298
325
|
else
|
|
299
326
|
# This is an InlineFragment or a FragmentSpread
|
|
300
327
|
if @runtime_directive_names.any? && node.directives.any? { |d| @runtime_directive_names.include?(d.name) }
|
|
301
|
-
next_selections =
|
|
302
|
-
next_selections
|
|
328
|
+
next_selections = {}
|
|
329
|
+
next_selections[:graphql_directives] = node.directives
|
|
303
330
|
if selections_to_run
|
|
304
331
|
selections_to_run << next_selections
|
|
305
332
|
else
|
|
@@ -316,25 +343,26 @@ module GraphQL
|
|
|
316
343
|
if node.type
|
|
317
344
|
type_defn = schema.get_type(node.type.name, context)
|
|
318
345
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
if
|
|
322
|
-
|
|
323
|
-
break
|
|
346
|
+
if query.warden.possible_types(type_defn).include?(owner_type)
|
|
347
|
+
result = gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
|
|
348
|
+
if !result.equal?(next_selections)
|
|
349
|
+
selections_to_run = result
|
|
324
350
|
end
|
|
325
351
|
end
|
|
326
352
|
else
|
|
327
353
|
# it's an untyped fragment, definitely continue
|
|
328
|
-
gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
|
|
354
|
+
result = gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
|
|
355
|
+
if !result.equal?(next_selections)
|
|
356
|
+
selections_to_run = result
|
|
357
|
+
end
|
|
329
358
|
end
|
|
330
359
|
when GraphQL::Language::Nodes::FragmentSpread
|
|
331
360
|
fragment_def = query.fragments[node.name]
|
|
332
361
|
type_defn = query.get_type(fragment_def.type.name)
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
if
|
|
336
|
-
|
|
337
|
-
break
|
|
362
|
+
if query.warden.possible_types(type_defn).include?(owner_type)
|
|
363
|
+
result = gather_selections(owner_object, owner_type, fragment_def.selections, selections_to_run, next_selections)
|
|
364
|
+
if !result.equal?(next_selections)
|
|
365
|
+
selections_to_run = result
|
|
338
366
|
end
|
|
339
367
|
end
|
|
340
368
|
else
|
|
@@ -345,33 +373,40 @@ module GraphQL
|
|
|
345
373
|
selections_to_run || selections_by_name
|
|
346
374
|
end
|
|
347
375
|
|
|
348
|
-
NO_ARGS =
|
|
376
|
+
NO_ARGS = GraphQL::EmptyObjects::EMPTY_HASH
|
|
349
377
|
|
|
350
378
|
# @return [void]
|
|
351
|
-
def evaluate_selections(
|
|
352
|
-
|
|
379
|
+
def evaluate_selections(owner_object, owner_type, is_eager_selection, gathered_selections, selections_result, target_result, parent_object) # rubocop:disable Metrics/ParameterLists
|
|
380
|
+
st = get_current_runtime_state
|
|
381
|
+
st.current_object = owner_object
|
|
382
|
+
st.current_result_name = nil
|
|
383
|
+
st.current_result = selections_result
|
|
353
384
|
|
|
354
385
|
finished_jobs = 0
|
|
355
386
|
enqueued_jobs = gathered_selections.size
|
|
356
387
|
gathered_selections.each do |result_name, field_ast_nodes_or_ast_node|
|
|
357
388
|
@dataloader.append_job {
|
|
358
389
|
evaluate_selection(
|
|
359
|
-
|
|
390
|
+
result_name, field_ast_nodes_or_ast_node, owner_object, owner_type, is_eager_selection, selections_result, parent_object
|
|
360
391
|
)
|
|
361
392
|
finished_jobs += 1
|
|
362
393
|
if target_result && finished_jobs == enqueued_jobs
|
|
363
|
-
|
|
394
|
+
selections_result.merge_into(target_result)
|
|
364
395
|
end
|
|
365
396
|
}
|
|
397
|
+
# Field resolution may pause the fiber,
|
|
398
|
+
# so it wouldn't get to the `Resolve` call that happens below.
|
|
399
|
+
# So instead trigger a run from this outer context.
|
|
400
|
+
if is_eager_selection
|
|
401
|
+
@dataloader.run
|
|
402
|
+
end
|
|
366
403
|
end
|
|
367
404
|
|
|
368
405
|
selections_result
|
|
369
406
|
end
|
|
370
407
|
|
|
371
|
-
attr_reader :progress_path
|
|
372
|
-
|
|
373
408
|
# @return [void]
|
|
374
|
-
def evaluate_selection(
|
|
409
|
+
def evaluate_selection(result_name, field_ast_nodes_or_ast_node, owner_object, owner_type, is_eager_field, selections_result, parent_object) # rubocop:disable Metrics/ParameterLists
|
|
375
410
|
return if dead_result?(selections_result)
|
|
376
411
|
# As a performance optimization, the hash key will be a `Node` if
|
|
377
412
|
# there's only one selection of the field. But if there are multiple
|
|
@@ -399,48 +434,54 @@ module GraphQL
|
|
|
399
434
|
raise "Invariant: no field for #{owner_type}.#{field_name}"
|
|
400
435
|
end
|
|
401
436
|
end
|
|
402
|
-
return_type = field_defn.type
|
|
403
437
|
|
|
404
|
-
|
|
405
|
-
next_path.freeze
|
|
438
|
+
return_type = field_defn.type
|
|
406
439
|
|
|
407
440
|
# This seems janky, but we need to know
|
|
408
441
|
# the field's return type at this path in order
|
|
409
442
|
# to propagate `null`
|
|
410
|
-
|
|
411
|
-
(selections_result.graphql_non_null_field_names ||= []).push(result_name)
|
|
412
|
-
end
|
|
443
|
+
return_type_non_null = return_type.non_null?
|
|
413
444
|
# Set this before calling `run_with_directives`, so that the directive can have the latest path
|
|
414
|
-
|
|
415
|
-
|
|
445
|
+
st = get_current_runtime_state
|
|
446
|
+
st.current_field = field_defn
|
|
447
|
+
st.current_result = selections_result
|
|
448
|
+
st.current_result_name = result_name
|
|
416
449
|
|
|
417
450
|
if is_introspection
|
|
418
|
-
|
|
451
|
+
owner_object = field_defn.owner.wrap(owner_object, context)
|
|
419
452
|
end
|
|
420
453
|
|
|
421
454
|
total_args_count = field_defn.arguments(context).size
|
|
422
455
|
if total_args_count == 0
|
|
423
456
|
resolved_arguments = GraphQL::Execution::Interpreter::Arguments::EMPTY
|
|
424
|
-
|
|
457
|
+
if field_defn.extras.size == 0
|
|
458
|
+
evaluate_selection_with_resolved_keyword_args(
|
|
459
|
+
NO_ARGS, resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, owner_object, is_eager_field, result_name, selections_result, parent_object, return_type, return_type_non_null
|
|
460
|
+
)
|
|
461
|
+
else
|
|
462
|
+
evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, owner_object, is_eager_field, result_name, selections_result, parent_object, return_type, return_type_non_null)
|
|
463
|
+
end
|
|
425
464
|
else
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
evaluate_selection_with_args(resolved_arguments, field_defn, next_path, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selections_result, parent_object)
|
|
465
|
+
@query.arguments_cache.dataload_for(ast_node, field_defn, owner_object) do |resolved_arguments|
|
|
466
|
+
evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, owner_object, is_eager_field, result_name, selections_result, parent_object, return_type, return_type_non_null)
|
|
429
467
|
end
|
|
430
468
|
end
|
|
431
469
|
end
|
|
432
470
|
|
|
433
|
-
def evaluate_selection_with_args(arguments, field_defn,
|
|
434
|
-
|
|
435
|
-
after_lazy(arguments, owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, owner_object: object, arguments: arguments, result_name: result_name, result: selection_result) do |resolved_arguments|
|
|
471
|
+
def evaluate_selection_with_args(arguments, field_defn, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selection_result, parent_object, return_type, return_type_non_null) # rubocop:disable Metrics/ParameterLists
|
|
472
|
+
after_lazy(arguments, field: field_defn, ast_node: ast_node, owner_object: object, arguments: arguments, result_name: result_name, result: selection_result) do |resolved_arguments|
|
|
436
473
|
if resolved_arguments.is_a?(GraphQL::ExecutionError) || resolved_arguments.is_a?(GraphQL::UnauthorizedError)
|
|
437
|
-
continue_value(
|
|
474
|
+
continue_value(resolved_arguments, owner_type, field_defn, return_type_non_null, ast_node, result_name, selection_result)
|
|
438
475
|
next
|
|
439
476
|
end
|
|
440
477
|
|
|
441
|
-
kwarg_arguments = if
|
|
442
|
-
|
|
443
|
-
|
|
478
|
+
kwarg_arguments = if field_defn.extras.empty?
|
|
479
|
+
if resolved_arguments.empty?
|
|
480
|
+
# We can avoid allocating the `{ Symbol => Object }` hash in this case
|
|
481
|
+
NO_ARGS
|
|
482
|
+
else
|
|
483
|
+
resolved_arguments.keyword_arguments
|
|
484
|
+
end
|
|
444
485
|
else
|
|
445
486
|
# Bundle up the extras, then make a new arguments instance
|
|
446
487
|
# that includes the extras, too.
|
|
@@ -450,9 +491,9 @@ module GraphQL
|
|
|
450
491
|
when :ast_node
|
|
451
492
|
extra_args[:ast_node] = ast_node
|
|
452
493
|
when :execution_errors
|
|
453
|
-
extra_args[:execution_errors] = ExecutionErrors.new(context, ast_node,
|
|
494
|
+
extra_args[:execution_errors] = ExecutionErrors.new(context, ast_node, current_path)
|
|
454
495
|
when :path
|
|
455
|
-
extra_args[:path] =
|
|
496
|
+
extra_args[:path] = current_path
|
|
456
497
|
when :lookahead
|
|
457
498
|
if !field_ast_nodes
|
|
458
499
|
field_ast_nodes = [ast_node]
|
|
@@ -479,67 +520,72 @@ module GraphQL
|
|
|
479
520
|
resolved_arguments.keyword_arguments
|
|
480
521
|
end
|
|
481
522
|
|
|
482
|
-
|
|
523
|
+
evaluate_selection_with_resolved_keyword_args(kwarg_arguments, resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selection_result, parent_object, return_type, return_type_non_null)
|
|
524
|
+
end
|
|
525
|
+
end
|
|
483
526
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
ex_err
|
|
510
|
-
end
|
|
527
|
+
def evaluate_selection_with_resolved_keyword_args(kwarg_arguments, resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selection_result, parent_object, return_type, return_type_non_null) # rubocop:disable Metrics/ParameterLists
|
|
528
|
+
st = get_current_runtime_state
|
|
529
|
+
st.current_field = field_defn
|
|
530
|
+
st.current_object = object
|
|
531
|
+
st.current_arguments = resolved_arguments
|
|
532
|
+
st.current_result_name = result_name
|
|
533
|
+
st.current_result = selection_result
|
|
534
|
+
# Optimize for the case that field is selected only once
|
|
535
|
+
if field_ast_nodes.nil? || field_ast_nodes.size == 1
|
|
536
|
+
next_selections = ast_node.selections
|
|
537
|
+
directives = ast_node.directives
|
|
538
|
+
else
|
|
539
|
+
next_selections = []
|
|
540
|
+
directives = []
|
|
541
|
+
field_ast_nodes.each { |f|
|
|
542
|
+
next_selections.concat(f.selections)
|
|
543
|
+
directives.concat(f.directives)
|
|
544
|
+
}
|
|
545
|
+
end
|
|
546
|
+
|
|
547
|
+
field_result = call_method_on_directives(:resolve, object, directives) do
|
|
548
|
+
# Actually call the field resolver and capture the result
|
|
549
|
+
app_result = begin
|
|
550
|
+
@current_trace.execute_field(field: field_defn, ast_node: ast_node, query: query, object: object, arguments: kwarg_arguments) do
|
|
551
|
+
field_defn.resolve(object, kwarg_arguments, context)
|
|
511
552
|
end
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
553
|
+
rescue GraphQL::ExecutionError => err
|
|
554
|
+
err
|
|
555
|
+
rescue StandardError => err
|
|
556
|
+
begin
|
|
557
|
+
query.handle_or_reraise(err)
|
|
558
|
+
rescue GraphQL::ExecutionError => ex_err
|
|
559
|
+
ex_err
|
|
517
560
|
end
|
|
518
561
|
end
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
Interpreter::Resolve.resolve_all([field_result], @dataloader)
|
|
525
|
-
else
|
|
526
|
-
# Return this from `after_lazy` because it might be another lazy that needs to be resolved
|
|
527
|
-
field_result
|
|
562
|
+
after_lazy(app_result, field: field_defn, ast_node: ast_node, owner_object: object, arguments: resolved_arguments, result_name: result_name, result: selection_result) do |inner_result|
|
|
563
|
+
continue_value = continue_value(inner_result, owner_type, field_defn, return_type_non_null, ast_node, result_name, selection_result)
|
|
564
|
+
if HALT != continue_value
|
|
565
|
+
continue_field(continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, resolved_arguments, result_name, selection_result)
|
|
566
|
+
end
|
|
528
567
|
end
|
|
529
568
|
end
|
|
569
|
+
|
|
570
|
+
# If this field is a root mutation field, immediately resolve
|
|
571
|
+
# all of its child fields before moving on to the next root mutation field.
|
|
572
|
+
# (Subselections of this mutation will still be resolved level-by-level.)
|
|
573
|
+
if is_eager_field
|
|
574
|
+
Interpreter::Resolve.resolve_all([field_result], @dataloader)
|
|
575
|
+
else
|
|
576
|
+
# Return this from `after_lazy` because it might be another lazy that needs to be resolved
|
|
577
|
+
field_result
|
|
578
|
+
end
|
|
530
579
|
end
|
|
531
580
|
|
|
581
|
+
|
|
532
582
|
def dead_result?(selection_result)
|
|
533
|
-
selection_result.graphql_dead || ((parent = selection_result.graphql_parent) && parent.graphql_dead)
|
|
583
|
+
selection_result.graphql_dead # || ((parent = selection_result.graphql_parent) && parent.graphql_dead)
|
|
534
584
|
end
|
|
535
585
|
|
|
536
|
-
def set_result(selection_result, result_name, value)
|
|
586
|
+
def set_result(selection_result, result_name, value, is_child_result, is_non_null)
|
|
537
587
|
if !dead_result?(selection_result)
|
|
538
|
-
if value.nil? &&
|
|
539
|
-
( # there are two conditions under which `nil` is not allowed in the response:
|
|
540
|
-
(selection_result.graphql_non_null_list_items) || # this value would be written into a list that doesn't allow nils
|
|
541
|
-
((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
|
|
542
|
-
)
|
|
588
|
+
if value.nil? && is_non_null
|
|
543
589
|
# This is an invalid nil that should be propagated
|
|
544
590
|
# One caller of this method passes a block,
|
|
545
591
|
# namely when application code returns a `nil` to GraphQL and it doesn't belong there.
|
|
@@ -549,15 +595,18 @@ module GraphQL
|
|
|
549
595
|
# TODO the code is trying to tell me something.
|
|
550
596
|
yield if block_given?
|
|
551
597
|
parent = selection_result.graphql_parent
|
|
552
|
-
name_in_parent = selection_result.graphql_result_name
|
|
553
598
|
if parent.nil? # This is a top-level result hash
|
|
554
599
|
@response = nil
|
|
555
600
|
else
|
|
556
|
-
|
|
601
|
+
name_in_parent = selection_result.graphql_result_name
|
|
602
|
+
is_non_null_in_parent = selection_result.graphql_is_non_null_in_parent
|
|
603
|
+
set_result(parent, name_in_parent, nil, false, is_non_null_in_parent)
|
|
557
604
|
set_graphql_dead(selection_result)
|
|
558
605
|
end
|
|
606
|
+
elsif is_child_result
|
|
607
|
+
selection_result.set_child_result(result_name, value)
|
|
559
608
|
else
|
|
560
|
-
selection_result
|
|
609
|
+
selection_result.set_leaf(result_name, value)
|
|
561
610
|
end
|
|
562
611
|
end
|
|
563
612
|
end
|
|
@@ -577,18 +626,29 @@ module GraphQL
|
|
|
577
626
|
end
|
|
578
627
|
end
|
|
579
628
|
|
|
629
|
+
def current_path
|
|
630
|
+
st = get_current_runtime_state
|
|
631
|
+
result = st.current_result
|
|
632
|
+
path = result && result.path
|
|
633
|
+
if path && (rn = st.current_result_name)
|
|
634
|
+
path = path.dup
|
|
635
|
+
path.push(rn)
|
|
636
|
+
end
|
|
637
|
+
path
|
|
638
|
+
end
|
|
639
|
+
|
|
580
640
|
HALT = Object.new
|
|
581
|
-
def continue_value(
|
|
641
|
+
def continue_value(value, parent_type, field, is_non_null, ast_node, result_name, selection_result) # rubocop:disable Metrics/ParameterLists
|
|
582
642
|
case value
|
|
583
643
|
when nil
|
|
584
644
|
if is_non_null
|
|
585
|
-
set_result(selection_result, result_name, nil) do
|
|
645
|
+
set_result(selection_result, result_name, nil, false, is_non_null) do
|
|
586
646
|
# This block is called if `result_name` is not dead. (Maybe a previous invalid nil caused it be marked dead.)
|
|
587
647
|
err = parent_type::InvalidNullError.new(parent_type, field, value)
|
|
588
648
|
schema.type_error(err, context)
|
|
589
649
|
end
|
|
590
650
|
else
|
|
591
|
-
set_result(selection_result, result_name, nil)
|
|
651
|
+
set_result(selection_result, result_name, nil, false, is_non_null)
|
|
592
652
|
end
|
|
593
653
|
HALT
|
|
594
654
|
when GraphQL::Error
|
|
@@ -597,14 +657,24 @@ module GraphQL
|
|
|
597
657
|
# every time.
|
|
598
658
|
if value.is_a?(GraphQL::ExecutionError)
|
|
599
659
|
if selection_result.nil? || !dead_result?(selection_result)
|
|
600
|
-
value.path ||=
|
|
660
|
+
value.path ||= current_path
|
|
601
661
|
value.ast_node ||= ast_node
|
|
602
662
|
context.errors << value
|
|
603
663
|
if selection_result
|
|
604
|
-
set_result(selection_result, result_name, nil)
|
|
664
|
+
set_result(selection_result, result_name, nil, false, is_non_null)
|
|
605
665
|
end
|
|
606
666
|
end
|
|
607
667
|
HALT
|
|
668
|
+
elsif value.is_a?(GraphQL::UnauthorizedFieldError)
|
|
669
|
+
value.field ||= field
|
|
670
|
+
# this hook might raise & crash, or it might return
|
|
671
|
+
# a replacement value
|
|
672
|
+
next_value = begin
|
|
673
|
+
schema.unauthorized_field(value)
|
|
674
|
+
rescue GraphQL::ExecutionError => err
|
|
675
|
+
err
|
|
676
|
+
end
|
|
677
|
+
continue_value(next_value, parent_type, field, is_non_null, ast_node, result_name, selection_result)
|
|
608
678
|
elsif value.is_a?(GraphQL::UnauthorizedError)
|
|
609
679
|
# this hook might raise & crash, or it might return
|
|
610
680
|
# a replacement value
|
|
@@ -613,7 +683,7 @@ module GraphQL
|
|
|
613
683
|
rescue GraphQL::ExecutionError => err
|
|
614
684
|
err
|
|
615
685
|
end
|
|
616
|
-
continue_value(
|
|
686
|
+
continue_value(next_value, parent_type, field, is_non_null, ast_node, result_name, selection_result)
|
|
617
687
|
elsif GraphQL::Execution::SKIP == value
|
|
618
688
|
# It's possible a lazy was already written here
|
|
619
689
|
case selection_result
|
|
@@ -639,15 +709,15 @@ module GraphQL
|
|
|
639
709
|
if selection_result.nil? || !dead_result?(selection_result)
|
|
640
710
|
value.each_with_index do |error, index|
|
|
641
711
|
error.ast_node ||= ast_node
|
|
642
|
-
error.path ||=
|
|
712
|
+
error.path ||= current_path + (list_type_at_all ? [index] : [])
|
|
643
713
|
context.errors << error
|
|
644
714
|
end
|
|
645
715
|
if selection_result
|
|
646
716
|
if list_type_at_all
|
|
647
717
|
result_without_errors = value.map { |v| v.is_a?(GraphQL::ExecutionError) ? nil : v }
|
|
648
|
-
set_result(selection_result, result_name, result_without_errors)
|
|
718
|
+
set_result(selection_result, result_name, result_without_errors, false, is_non_null)
|
|
649
719
|
else
|
|
650
|
-
set_result(selection_result, result_name, nil)
|
|
720
|
+
set_result(selection_result, result_name, nil, false, is_non_null)
|
|
651
721
|
end
|
|
652
722
|
end
|
|
653
723
|
end
|
|
@@ -657,7 +727,7 @@ module GraphQL
|
|
|
657
727
|
end
|
|
658
728
|
when GraphQL::Execution::Interpreter::RawValue
|
|
659
729
|
# Write raw value directly to the response without resolving nested objects
|
|
660
|
-
set_result(selection_result, result_name, value.resolve)
|
|
730
|
+
set_result(selection_result, result_name, value.resolve, false, is_non_null)
|
|
661
731
|
HALT
|
|
662
732
|
else
|
|
663
733
|
value
|
|
@@ -672,7 +742,7 @@ module GraphQL
|
|
|
672
742
|
# Location information from `path` and `ast_node`.
|
|
673
743
|
#
|
|
674
744
|
# @return [Lazy, Array, Hash, Object] Lazy, Array, and Hash are all traversed to resolve lazy values later
|
|
675
|
-
def continue_field(
|
|
745
|
+
def continue_field(value, owner_type, field, current_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result) # rubocop:disable Metrics/ParameterLists
|
|
676
746
|
if current_type.non_null?
|
|
677
747
|
current_type = current_type.of_type
|
|
678
748
|
is_non_null = true
|
|
@@ -680,12 +750,16 @@ module GraphQL
|
|
|
680
750
|
|
|
681
751
|
case current_type.kind.name
|
|
682
752
|
when "SCALAR", "ENUM"
|
|
683
|
-
r =
|
|
684
|
-
|
|
753
|
+
r = begin
|
|
754
|
+
current_type.coerce_result(value, context)
|
|
755
|
+
rescue StandardError => err
|
|
756
|
+
schema.handle_or_reraise(context, err)
|
|
757
|
+
end
|
|
758
|
+
set_result(selection_result, result_name, r, false, is_non_null)
|
|
685
759
|
r
|
|
686
760
|
when "UNION", "INTERFACE"
|
|
687
|
-
resolved_type_or_lazy = resolve_type(current_type, value
|
|
688
|
-
after_lazy(resolved_type_or_lazy,
|
|
761
|
+
resolved_type_or_lazy = resolve_type(current_type, value)
|
|
762
|
+
after_lazy(resolved_type_or_lazy, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result) do |resolved_type_result|
|
|
689
763
|
if resolved_type_result.is_a?(Array) && resolved_type_result.length == 2
|
|
690
764
|
resolved_type, resolved_value = resolved_type_result
|
|
691
765
|
else
|
|
@@ -699,23 +773,23 @@ module GraphQL
|
|
|
699
773
|
err_class = current_type::UnresolvedTypeError
|
|
700
774
|
type_error = err_class.new(resolved_value, field, parent_type, resolved_type, possible_types)
|
|
701
775
|
schema.type_error(type_error, context)
|
|
702
|
-
set_result(selection_result, result_name, nil)
|
|
776
|
+
set_result(selection_result, result_name, nil, false, is_non_null)
|
|
703
777
|
nil
|
|
704
778
|
else
|
|
705
|
-
continue_field(
|
|
779
|
+
continue_field(resolved_value, owner_type, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result)
|
|
706
780
|
end
|
|
707
781
|
end
|
|
708
782
|
when "OBJECT"
|
|
709
783
|
object_proxy = begin
|
|
710
|
-
|
|
784
|
+
current_type.wrap(value, context)
|
|
711
785
|
rescue GraphQL::ExecutionError => err
|
|
712
786
|
err
|
|
713
787
|
end
|
|
714
|
-
after_lazy(object_proxy,
|
|
715
|
-
continue_value = continue_value(
|
|
788
|
+
after_lazy(object_proxy, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result) do |inner_object|
|
|
789
|
+
continue_value = continue_value(inner_object, owner_type, field, is_non_null, ast_node, result_name, selection_result)
|
|
716
790
|
if HALT != continue_value
|
|
717
|
-
response_hash = GraphQLResultHash.new(result_name, selection_result)
|
|
718
|
-
set_result(selection_result, result_name, response_hash)
|
|
791
|
+
response_hash = GraphQLResultHash.new(result_name, selection_result, is_non_null)
|
|
792
|
+
set_result(selection_result, result_name, response_hash, true, is_non_null)
|
|
719
793
|
gathered_selections = gather_selections(continue_value, current_type, next_selections)
|
|
720
794
|
# There are two possibilities for `gathered_selections`:
|
|
721
795
|
# 1. All selections of this object should be evaluated together (there are no runtime directives modifying execution).
|
|
@@ -727,16 +801,25 @@ module GraphQL
|
|
|
727
801
|
# (Technically, it's possible that one of those entries _doesn't_ require isolation.)
|
|
728
802
|
tap_or_each(gathered_selections) do |selections, is_selection_array|
|
|
729
803
|
if is_selection_array
|
|
730
|
-
this_result = GraphQLResultHash.new(result_name, selection_result)
|
|
804
|
+
this_result = GraphQLResultHash.new(result_name, selection_result, is_non_null)
|
|
731
805
|
final_result = response_hash
|
|
732
806
|
else
|
|
733
807
|
this_result = response_hash
|
|
734
808
|
final_result = nil
|
|
735
809
|
end
|
|
736
|
-
|
|
737
|
-
|
|
810
|
+
# reset this mutable state
|
|
811
|
+
# Unset `result_name` here because it's already included in the new response hash
|
|
812
|
+
st = get_current_runtime_state
|
|
813
|
+
st.current_object = continue_value
|
|
814
|
+
st.current_result_name = nil
|
|
815
|
+
st.current_result = this_result
|
|
816
|
+
|
|
817
|
+
# This is a less-frequent case; use a fast check since it's often not there.
|
|
818
|
+
if (directives = selections[:graphql_directives])
|
|
819
|
+
selections.delete(:graphql_directives)
|
|
820
|
+
end
|
|
821
|
+
call_method_on_directives(:resolve, continue_value, directives) do
|
|
738
822
|
evaluate_selections(
|
|
739
|
-
path,
|
|
740
823
|
continue_value,
|
|
741
824
|
current_type,
|
|
742
825
|
false,
|
|
@@ -754,43 +837,30 @@ module GraphQL
|
|
|
754
837
|
inner_type = current_type.of_type
|
|
755
838
|
# This is true for objects, unions, and interfaces
|
|
756
839
|
use_dataloader_job = !inner_type.unwrap.kind.input?
|
|
757
|
-
|
|
758
|
-
response_list
|
|
759
|
-
set_result(selection_result, result_name, response_list)
|
|
760
|
-
|
|
761
|
-
idx = 0
|
|
840
|
+
inner_type_non_null = inner_type.non_null?
|
|
841
|
+
response_list = GraphQLResultArray.new(result_name, selection_result, is_non_null)
|
|
842
|
+
set_result(selection_result, result_name, response_list, true, is_non_null)
|
|
843
|
+
idx = nil
|
|
762
844
|
list_value = begin
|
|
763
845
|
value.each do |inner_value|
|
|
764
|
-
|
|
765
|
-
if !result_was_set
|
|
766
|
-
# Don't set the result unless `.each` is successful
|
|
767
|
-
set_result(selection_result, result_name, response_list)
|
|
768
|
-
result_was_set = true
|
|
769
|
-
end
|
|
770
|
-
next_path = path + [idx]
|
|
846
|
+
idx ||= 0
|
|
771
847
|
this_idx = idx
|
|
772
|
-
next_path.freeze
|
|
773
848
|
idx += 1
|
|
774
849
|
if use_dataloader_job
|
|
775
850
|
@dataloader.append_job do
|
|
776
|
-
resolve_list_item(inner_value, inner_type,
|
|
851
|
+
resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type)
|
|
777
852
|
end
|
|
778
853
|
else
|
|
779
|
-
resolve_list_item(inner_value, inner_type,
|
|
854
|
+
resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type)
|
|
780
855
|
end
|
|
781
856
|
end
|
|
782
|
-
# Maybe the list was empty and the block was never called.
|
|
783
|
-
if !result_was_set
|
|
784
|
-
set_result(selection_result, result_name, response_list)
|
|
785
|
-
result_was_set = true
|
|
786
|
-
end
|
|
787
857
|
|
|
788
858
|
response_list
|
|
789
859
|
rescue NoMethodError => err
|
|
790
860
|
# Ruby 2.2 doesn't have NoMethodError#receiver, can't check that one in this case. (It's been EOL since 2017.)
|
|
791
861
|
if err.name == :each && (err.respond_to?(:receiver) ? err.receiver == value : true)
|
|
792
862
|
# This happens when the GraphQL schema doesn't match the implementation. Help the dev debug.
|
|
793
|
-
raise ListResultFailedError.new(value: value, field: field, path:
|
|
863
|
+
raise ListResultFailedError.new(value: value, field: field, path: current_path)
|
|
794
864
|
else
|
|
795
865
|
# This was some other NoMethodError -- let it bubble to reveal the real error.
|
|
796
866
|
raise
|
|
@@ -804,21 +874,24 @@ module GraphQL
|
|
|
804
874
|
ex_err
|
|
805
875
|
end
|
|
806
876
|
end
|
|
807
|
-
|
|
808
|
-
|
|
877
|
+
# Detect whether this error came while calling `.each` (before `idx` is set) or while running list *items* (after `idx` is set)
|
|
878
|
+
error_is_non_null = idx.nil? ? is_non_null : inner_type.non_null?
|
|
879
|
+
continue_value(list_value, owner_type, field, error_is_non_null, ast_node, result_name, selection_result)
|
|
809
880
|
else
|
|
810
881
|
raise "Invariant: Unhandled type kind #{current_type.kind} (#{current_type})"
|
|
811
882
|
end
|
|
812
883
|
end
|
|
813
884
|
|
|
814
|
-
def resolve_list_item(inner_value, inner_type,
|
|
815
|
-
|
|
885
|
+
def resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, next_selections, owner_type) # rubocop:disable Metrics/ParameterLists
|
|
886
|
+
st = get_current_runtime_state
|
|
887
|
+
st.current_result_name = this_idx
|
|
888
|
+
st.current_result = response_list
|
|
816
889
|
call_method_on_directives(:resolve_each, owner_object, ast_node.directives) do
|
|
817
890
|
# This will update `response_list` with the lazy
|
|
818
|
-
after_lazy(inner_value,
|
|
819
|
-
continue_value = continue_value(
|
|
891
|
+
after_lazy(inner_value, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, result_name: this_idx, result: response_list) do |inner_inner_value|
|
|
892
|
+
continue_value = continue_value(inner_inner_value, owner_type, field, inner_type_non_null, ast_node, this_idx, response_list)
|
|
820
893
|
if HALT != continue_value
|
|
821
|
-
continue_field(
|
|
894
|
+
continue_field(continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments, this_idx, response_list)
|
|
822
895
|
end
|
|
823
896
|
end
|
|
824
897
|
end
|
|
@@ -837,7 +910,6 @@ module GraphQL
|
|
|
837
910
|
dir_defn = @schema_directives.fetch(dir_node.name)
|
|
838
911
|
raw_dir_args = arguments(nil, dir_defn, dir_node)
|
|
839
912
|
dir_args = continue_value(
|
|
840
|
-
@context[:current_path], # path
|
|
841
913
|
raw_dir_args, # value
|
|
842
914
|
dir_defn, # parent_type
|
|
843
915
|
nil, # field
|
|
@@ -869,36 +941,48 @@ module GraphQL
|
|
|
869
941
|
true
|
|
870
942
|
end
|
|
871
943
|
|
|
872
|
-
def
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
@context[:current_field] = @interpreter_context[:current_field] = field
|
|
944
|
+
def get_current_runtime_state
|
|
945
|
+
current_state = Thread.current[:__graphql_runtime_info] ||= begin
|
|
946
|
+
per_query_state = {}
|
|
947
|
+
per_query_state.compare_by_identity
|
|
948
|
+
per_query_state
|
|
878
949
|
end
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
950
|
+
|
|
951
|
+
current_state[@query] ||= CurrentState.new
|
|
952
|
+
end
|
|
953
|
+
|
|
954
|
+
def minimal_after_lazy(value, &block)
|
|
955
|
+
if lazy?(value)
|
|
956
|
+
GraphQL::Execution::Lazy.new do
|
|
957
|
+
result = @schema.sync_lazy(value)
|
|
958
|
+
# The returned result might also be lazy, so check it, too
|
|
959
|
+
minimal_after_lazy(result, &block)
|
|
960
|
+
end
|
|
961
|
+
else
|
|
962
|
+
yield(value)
|
|
884
963
|
end
|
|
885
964
|
end
|
|
886
965
|
|
|
887
966
|
# @param obj [Object] Some user-returned value that may want to be batched
|
|
888
|
-
# @param path [Array<String>]
|
|
889
967
|
# @param field [GraphQL::Schema::Field]
|
|
890
968
|
# @param eager [Boolean] Set to `true` for mutation root fields only
|
|
891
969
|
# @param trace [Boolean] If `false`, don't wrap this with field tracing
|
|
892
970
|
# @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.
|
|
893
|
-
def after_lazy(lazy_obj,
|
|
971
|
+
def after_lazy(lazy_obj, field:, owner_object:, arguments:, ast_node:, result:, result_name:, eager: false, trace: true, &block)
|
|
894
972
|
if lazy?(lazy_obj)
|
|
895
|
-
|
|
896
|
-
|
|
973
|
+
orig_result = result
|
|
974
|
+
lazy = GraphQL::Execution::Lazy.new(field: field) do
|
|
975
|
+
st = get_current_runtime_state
|
|
976
|
+
st.current_object = owner_object
|
|
977
|
+
st.current_field = field
|
|
978
|
+
st.current_arguments = arguments
|
|
979
|
+
st.current_result_name = result_name
|
|
980
|
+
st.current_result = orig_result
|
|
897
981
|
# Wrap the execution of _this_ method with tracing,
|
|
898
982
|
# but don't wrap the continuation below
|
|
899
983
|
inner_obj = begin
|
|
900
984
|
if trace
|
|
901
|
-
|
|
985
|
+
@current_trace.execute_field_lazy(field: field, query: query, object: owner_object, arguments: arguments, ast_node: ast_node) do
|
|
902
986
|
schema.sync_lazy(lazy_obj)
|
|
903
987
|
end
|
|
904
988
|
else
|
|
@@ -919,11 +1003,17 @@ module GraphQL
|
|
|
919
1003
|
if eager
|
|
920
1004
|
lazy.value
|
|
921
1005
|
else
|
|
922
|
-
set_result(result, result_name, lazy)
|
|
1006
|
+
set_result(result, result_name, lazy, false, false) # is_non_null is irrelevant here
|
|
1007
|
+
current_depth = 0
|
|
1008
|
+
while result
|
|
1009
|
+
current_depth += 1
|
|
1010
|
+
result = result.graphql_parent
|
|
1011
|
+
end
|
|
1012
|
+
@lazies_at_depth[current_depth] << lazy
|
|
923
1013
|
lazy
|
|
924
1014
|
end
|
|
925
1015
|
else
|
|
926
|
-
|
|
1016
|
+
# Don't need to reset state here because it _wasn't_ lazy.
|
|
927
1017
|
yield(lazy_obj)
|
|
928
1018
|
end
|
|
929
1019
|
end
|
|
@@ -937,27 +1027,25 @@ module GraphQL
|
|
|
937
1027
|
end
|
|
938
1028
|
end
|
|
939
1029
|
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
@context.delete(key)
|
|
1030
|
+
def delete_all_interpreter_context
|
|
1031
|
+
per_query_state = Thread.current[:__graphql_runtime_info]
|
|
1032
|
+
if per_query_state
|
|
1033
|
+
per_query_state.delete(@query)
|
|
1034
|
+
if per_query_state.size == 0
|
|
1035
|
+
Thread.current[:__graphql_runtime_info] = nil
|
|
1036
|
+
end
|
|
1037
|
+
end
|
|
1038
|
+
nil
|
|
950
1039
|
end
|
|
951
1040
|
|
|
952
|
-
def resolve_type(type, value
|
|
953
|
-
|
|
954
|
-
resolved_type, resolved_value = query.trace("resolve_type", trace_payload) do
|
|
1041
|
+
def resolve_type(type, value)
|
|
1042
|
+
resolved_type, resolved_value = @current_trace.resolve_type(query: query, type: type, object: value) do
|
|
955
1043
|
query.resolve_type(type, value)
|
|
956
1044
|
end
|
|
957
1045
|
|
|
958
1046
|
if lazy?(resolved_type)
|
|
959
1047
|
GraphQL::Execution::Lazy.new do
|
|
960
|
-
|
|
1048
|
+
@current_trace.resolve_type_lazy(query: query, type: type, object: value) do
|
|
961
1049
|
schema.sync_lazy(resolved_type)
|
|
962
1050
|
end
|
|
963
1051
|
end
|
|
@@ -966,14 +1054,13 @@ module GraphQL
|
|
|
966
1054
|
end
|
|
967
1055
|
end
|
|
968
1056
|
|
|
969
|
-
def authorized_new(type, value, context)
|
|
970
|
-
type.authorized_new(value, context)
|
|
971
|
-
end
|
|
972
|
-
|
|
973
1057
|
def lazy?(object)
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
1058
|
+
obj_class = object.class
|
|
1059
|
+
is_lazy = @lazy_cache[obj_class]
|
|
1060
|
+
if is_lazy.nil?
|
|
1061
|
+
is_lazy = @lazy_cache[obj_class] = @schema.lazy?(object)
|
|
1062
|
+
end
|
|
1063
|
+
is_lazy
|
|
977
1064
|
end
|
|
978
1065
|
end
|
|
979
1066
|
end
|