graphql 2.0.16 → 2.0.21
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/analysis/ast/visitor.rb +42 -35
- data/lib/graphql/analysis/ast.rb +2 -2
- data/lib/graphql/backtrace/trace.rb +96 -0
- data/lib/graphql/backtrace/tracer.rb +1 -1
- data/lib/graphql/backtrace.rb +6 -1
- data/lib/graphql/execution/interpreter/arguments.rb +1 -1
- data/lib/graphql/execution/interpreter/arguments_cache.rb +2 -3
- data/lib/graphql/execution/interpreter/resolve.rb +19 -0
- data/lib/graphql/execution/interpreter/runtime.rb +264 -211
- data/lib/graphql/execution/interpreter.rb +15 -10
- data/lib/graphql/execution/lazy.rb +6 -12
- data/lib/graphql/execution/multiplex.rb +2 -1
- data/lib/graphql/filter.rb +7 -2
- data/lib/graphql/introspection/directive_type.rb +2 -2
- 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/language/document_from_schema_definition.rb +25 -9
- data/lib/graphql/language/lexer.rb +216 -1505
- data/lib/graphql/language/nodes.rb +66 -40
- data/lib/graphql/language/parser.rb +509 -491
- data/lib/graphql/language/parser.y +43 -38
- data/lib/graphql/language/visitor.rb +191 -83
- 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 +62 -31
- data/lib/graphql/query/null_context.rb +1 -1
- data/lib/graphql/query.rb +22 -5
- data/lib/graphql/schema/argument.rb +7 -13
- data/lib/graphql/schema/build_from_definition.rb +15 -3
- data/lib/graphql/schema/directive.rb +12 -2
- data/lib/graphql/schema/enum.rb +24 -17
- data/lib/graphql/schema/enum_value.rb +2 -3
- data/lib/graphql/schema/field.rb +68 -57
- data/lib/graphql/schema/field_extension.rb +1 -4
- data/lib/graphql/schema/find_inherited_value.rb +2 -7
- data/lib/graphql/schema/interface.rb +0 -10
- 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/has_arguments.rb +105 -58
- 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 +15 -10
- 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 +2 -4
- data/lib/graphql/schema/resolver/has_payload_type.rb +9 -9
- data/lib/graphql/schema/resolver.rb +4 -4
- data/lib/graphql/schema/timeout.rb +24 -28
- data/lib/graphql/schema/validator.rb +1 -1
- data/lib/graphql/schema/warden.rb +29 -5
- data/lib/graphql/schema.rb +76 -25
- 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/event.rb +2 -7
- data/lib/graphql/tracing/active_support_notifications_trace.rb +16 -0
- data/lib/graphql/tracing/appoptics_trace.rb +231 -0
- data/lib/graphql/tracing/appsignal_trace.rb +77 -0
- data/lib/graphql/tracing/data_dog_trace.rb +148 -0
- data/lib/graphql/tracing/legacy_trace.rb +65 -0
- data/lib/graphql/tracing/new_relic_trace.rb +75 -0
- data/lib/graphql/tracing/notifications_trace.rb +42 -0
- data/lib/graphql/tracing/platform_trace.rb +109 -0
- data/lib/graphql/tracing/platform_tracing.rb +15 -3
- data/lib/graphql/tracing/prometheus_trace.rb +89 -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 +72 -0
- data/lib/graphql/tracing/statsd_trace.rb +56 -0
- data/lib/graphql/tracing/trace.rb +75 -0
- data/lib/graphql/tracing.rb +16 -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 +24 -6
- data/lib/graphql/types/relay/edge_behaviors.rb +16 -6
- data/lib/graphql/types/relay/node_behaviors.rb +7 -1
- 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
- metadata +34 -9
- data/lib/graphql/language/lexer.rl +0 -280
- data/lib/graphql/types/relay/default_relay.rb +0 -27
@@ -8,6 +8,18 @@ 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
25
|
def initialize(result_name, parent_result)
|
@@ -20,6 +32,15 @@ module GraphQL
|
|
20
32
|
@graphql_metadata = nil
|
21
33
|
end
|
22
34
|
|
35
|
+
def path
|
36
|
+
@path ||= build_path([])
|
37
|
+
end
|
38
|
+
|
39
|
+
def build_path(path_array)
|
40
|
+
graphql_result_name && path_array.unshift(graphql_result_name)
|
41
|
+
@graphql_parent ? @graphql_parent.build_path(path_array) : path_array
|
42
|
+
end
|
43
|
+
|
23
44
|
attr_accessor :graphql_dead
|
24
45
|
attr_reader :graphql_parent, :graphql_result_name
|
25
46
|
|
@@ -45,7 +66,7 @@ module GraphQL
|
|
45
66
|
|
46
67
|
attr_accessor :graphql_merged_into
|
47
68
|
|
48
|
-
def
|
69
|
+
def set_leaf(key, value)
|
49
70
|
# This is a hack.
|
50
71
|
# Basically, this object is merged into the root-level result at some point.
|
51
72
|
# But the problem is, some lazies are created whose closures retain reference to _this_
|
@@ -55,23 +76,27 @@ module GraphQL
|
|
55
76
|
# In order to return a proper partial result (eg, for a directive), we have to update this object, too.
|
56
77
|
# Yowza.
|
57
78
|
if (t = @graphql_merged_into)
|
58
|
-
t
|
79
|
+
t.set_leaf(key, value)
|
59
80
|
end
|
60
81
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
# then create the metadata hash if necessary. It will be kept up-to-date after this.
|
65
|
-
(@graphql_metadata ||= @graphql_result_data.dup)[key] = value
|
66
|
-
else
|
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
|
82
|
+
@graphql_result_data[key] = value
|
83
|
+
# keep this up-to-date if it's been initialized
|
84
|
+
@graphql_metadata && @graphql_metadata[key] = value
|
71
85
|
|
72
86
|
value
|
73
87
|
end
|
74
88
|
|
89
|
+
def set_child_result(key, value)
|
90
|
+
if (t = @graphql_merged_into)
|
91
|
+
t.set_child_result(key, value)
|
92
|
+
end
|
93
|
+
@graphql_result_data[key] = value.graphql_result_data
|
94
|
+
# If we encounter some part of this response that requires metadata tracking,
|
95
|
+
# then create the metadata hash if necessary. It will be kept up-to-date after this.
|
96
|
+
(@graphql_metadata ||= @graphql_result_data.dup)[key] = value
|
97
|
+
value
|
98
|
+
end
|
99
|
+
|
75
100
|
def delete(key)
|
76
101
|
@graphql_metadata && @graphql_metadata.delete(key)
|
77
102
|
@graphql_result_data.delete(key)
|
@@ -92,6 +117,29 @@ module GraphQL
|
|
92
117
|
def [](k)
|
93
118
|
(@graphql_metadata || @graphql_result_data)[k]
|
94
119
|
end
|
120
|
+
|
121
|
+
def merge_into(into_result)
|
122
|
+
self.each do |key, value|
|
123
|
+
case value
|
124
|
+
when GraphQLResultHash
|
125
|
+
next_into = into_result[key]
|
126
|
+
if next_into
|
127
|
+
value.merge_into(next_into)
|
128
|
+
else
|
129
|
+
into_result.set_child_result(key, value)
|
130
|
+
end
|
131
|
+
when GraphQLResultArray
|
132
|
+
# There's no special handling of arrays because currently, there's no way to split the execution
|
133
|
+
# of a list over several concurrent flows.
|
134
|
+
next_result.set_child_result(key, value)
|
135
|
+
else
|
136
|
+
# We have to assume that, since this passed the `fields_will_merge` selection,
|
137
|
+
# that the old and new values are the same.
|
138
|
+
into_result.set_leaf(key, value)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
@graphql_merged_into = into_result
|
142
|
+
end
|
95
143
|
end
|
96
144
|
|
97
145
|
class GraphQLResultArray
|
@@ -114,19 +162,25 @@ module GraphQL
|
|
114
162
|
@graphql_result_data.delete_at(delete_at_index)
|
115
163
|
end
|
116
164
|
|
117
|
-
def
|
165
|
+
def set_leaf(idx, value)
|
118
166
|
if @skip_indices
|
119
167
|
offset_by = @skip_indices.count { |skipped_idx| skipped_idx < idx }
|
120
168
|
idx -= offset_by
|
121
169
|
end
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
@graphql_result_data[idx] = value
|
127
|
-
@graphql_metadata && @graphql_metadata[idx] = value
|
128
|
-
end
|
170
|
+
@graphql_result_data[idx] = value
|
171
|
+
@graphql_metadata && @graphql_metadata[idx] = value
|
172
|
+
value
|
173
|
+
end
|
129
174
|
|
175
|
+
def set_child_result(idx, value)
|
176
|
+
if @skip_indices
|
177
|
+
offset_by = @skip_indices.count { |skipped_idx| skipped_idx < idx }
|
178
|
+
idx -= offset_by
|
179
|
+
end
|
180
|
+
@graphql_result_data[idx] = value.graphql_result_data
|
181
|
+
# If we encounter some part of this response that requires metadata tracking,
|
182
|
+
# then create the metadata hash if necessary. It will be kept up-to-date after this.
|
183
|
+
(@graphql_metadata ||= @graphql_result_data.dup)[idx] = value
|
130
184
|
value
|
131
185
|
end
|
132
186
|
|
@@ -148,18 +202,10 @@ module GraphQL
|
|
148
202
|
# @return [GraphQL::Query::Context]
|
149
203
|
attr_reader :context
|
150
204
|
|
151
|
-
def
|
152
|
-
info = Thread.current[:__graphql_runtime_info]
|
153
|
-
if !info
|
154
|
-
new_ti = {}
|
155
|
-
info = Thread.current[:__graphql_runtime_info] = new_ti
|
156
|
-
end
|
157
|
-
info
|
158
|
-
end
|
159
|
-
|
160
|
-
def initialize(query:)
|
205
|
+
def initialize(query:, lazies_at_depth:)
|
161
206
|
@query = query
|
162
207
|
@dataloader = query.multiplex.dataloader
|
208
|
+
@lazies_at_depth = lazies_at_depth
|
163
209
|
@schema = query.schema
|
164
210
|
@context = query.context
|
165
211
|
@multiplex_context = query.multiplex.context
|
@@ -208,8 +254,9 @@ module GraphQL
|
|
208
254
|
root_operation = query.selected_operation
|
209
255
|
root_op_type = root_operation.operation_type || "query"
|
210
256
|
root_type = schema.root_type_for_operation(root_op_type)
|
211
|
-
|
212
|
-
|
257
|
+
st = get_current_runtime_state
|
258
|
+
st.current_object = query.root_value
|
259
|
+
st.current_result = @response
|
213
260
|
object_proxy = authorized_new(root_type, query.root_value, context)
|
214
261
|
object_proxy = schema.sync_lazy(object_proxy)
|
215
262
|
|
@@ -236,10 +283,12 @@ module GraphQL
|
|
236
283
|
end
|
237
284
|
|
238
285
|
@dataloader.append_job {
|
239
|
-
|
286
|
+
st = get_current_runtime_state
|
287
|
+
st.current_object = query.root_value
|
288
|
+
st.current_result = selection_response
|
289
|
+
|
240
290
|
call_method_on_directives(:resolve, object_proxy, selections.graphql_directives) do
|
241
291
|
evaluate_selections(
|
242
|
-
path,
|
243
292
|
object_proxy,
|
244
293
|
root_type,
|
245
294
|
root_op_type == "mutation",
|
@@ -253,32 +302,7 @@ module GraphQL
|
|
253
302
|
end
|
254
303
|
end
|
255
304
|
end
|
256
|
-
|
257
|
-
delete_interpreter_context(:current_field)
|
258
|
-
delete_interpreter_context(:current_object)
|
259
|
-
delete_interpreter_context(:current_arguments)
|
260
|
-
nil
|
261
|
-
end
|
262
|
-
|
263
|
-
# @return [void]
|
264
|
-
def deep_merge_selection_result(from_result, into_result)
|
265
|
-
from_result.each do |key, value|
|
266
|
-
if !into_result.key?(key)
|
267
|
-
into_result[key] = value
|
268
|
-
else
|
269
|
-
case value
|
270
|
-
when GraphQLResultHash
|
271
|
-
deep_merge_selection_result(value, into_result[key])
|
272
|
-
else
|
273
|
-
# We have to assume that, since this passed the `fields_will_merge` selection,
|
274
|
-
# that the old and new values are the same.
|
275
|
-
# There's no special handling of arrays because currently, there's no way to split the execution
|
276
|
-
# of a list over several concurrent flows.
|
277
|
-
into_result[key] = value
|
278
|
-
end
|
279
|
-
end
|
280
|
-
end
|
281
|
-
from_result.graphql_merged_into = into_result
|
305
|
+
delete_all_interpreter_context
|
282
306
|
nil
|
283
307
|
end
|
284
308
|
|
@@ -355,22 +379,25 @@ module GraphQL
|
|
355
379
|
selections_to_run || selections_by_name
|
356
380
|
end
|
357
381
|
|
358
|
-
NO_ARGS =
|
382
|
+
NO_ARGS = GraphQL::EmptyObjects::EMPTY_HASH
|
359
383
|
|
360
384
|
# @return [void]
|
361
|
-
def evaluate_selections(
|
362
|
-
|
385
|
+
def evaluate_selections(owner_object, owner_type, is_eager_selection, gathered_selections, selections_result, target_result, parent_object) # rubocop:disable Metrics/ParameterLists
|
386
|
+
st = get_current_runtime_state
|
387
|
+
st.current_object = owner_object
|
388
|
+
st.current_result_name = nil
|
389
|
+
st.current_result = selections_result
|
363
390
|
|
364
391
|
finished_jobs = 0
|
365
392
|
enqueued_jobs = gathered_selections.size
|
366
393
|
gathered_selections.each do |result_name, field_ast_nodes_or_ast_node|
|
367
394
|
@dataloader.append_job {
|
368
395
|
evaluate_selection(
|
369
|
-
|
396
|
+
result_name, field_ast_nodes_or_ast_node, owner_object, owner_type, is_eager_selection, selections_result, parent_object
|
370
397
|
)
|
371
398
|
finished_jobs += 1
|
372
399
|
if target_result && finished_jobs == enqueued_jobs
|
373
|
-
|
400
|
+
selections_result.merge_into(target_result)
|
374
401
|
end
|
375
402
|
}
|
376
403
|
end
|
@@ -378,10 +405,8 @@ module GraphQL
|
|
378
405
|
selections_result
|
379
406
|
end
|
380
407
|
|
381
|
-
attr_reader :progress_path
|
382
|
-
|
383
408
|
# @return [void]
|
384
|
-
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
|
385
410
|
return if dead_result?(selections_result)
|
386
411
|
# As a performance optimization, the hash key will be a `Node` if
|
387
412
|
# there's only one selection of the field. But if there are multiple
|
@@ -409,19 +434,22 @@ module GraphQL
|
|
409
434
|
raise "Invariant: no field for #{owner_type}.#{field_name}"
|
410
435
|
end
|
411
436
|
end
|
412
|
-
return_type = field_defn.type
|
413
437
|
|
414
|
-
|
415
|
-
next_path.freeze
|
438
|
+
return_type = field_defn.type
|
416
439
|
|
417
440
|
# This seems janky, but we need to know
|
418
441
|
# the field's return type at this path in order
|
419
442
|
# to propagate `null`
|
420
|
-
|
443
|
+
return_type_non_null = return_type.non_null?
|
444
|
+
if return_type_non_null
|
421
445
|
(selections_result.graphql_non_null_field_names ||= []).push(result_name)
|
422
446
|
end
|
423
447
|
# Set this before calling `run_with_directives`, so that the directive can have the latest path
|
424
|
-
|
448
|
+
st = get_current_runtime_state
|
449
|
+
st.current_field = field_defn
|
450
|
+
st.current_result = selections_result
|
451
|
+
st.current_result_name = result_name
|
452
|
+
|
425
453
|
object = owner_object
|
426
454
|
|
427
455
|
if is_introspection
|
@@ -431,26 +459,34 @@ module GraphQL
|
|
431
459
|
total_args_count = field_defn.arguments(context).size
|
432
460
|
if total_args_count == 0
|
433
461
|
resolved_arguments = GraphQL::Execution::Interpreter::Arguments::EMPTY
|
434
|
-
|
462
|
+
if field_defn.extras.size == 0
|
463
|
+
evaluate_selection_with_resolved_keyword_args(
|
464
|
+
NO_ARGS, resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selections_result, parent_object, return_type, return_type_non_null
|
465
|
+
)
|
466
|
+
else
|
467
|
+
evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selections_result, parent_object, return_type, return_type_non_null)
|
468
|
+
end
|
435
469
|
else
|
436
|
-
# TODO remove all arguments(...) usages?
|
437
470
|
@query.arguments_cache.dataload_for(ast_node, field_defn, object) do |resolved_arguments|
|
438
|
-
evaluate_selection_with_args(resolved_arguments, field_defn,
|
471
|
+
evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type, object, is_eager_field, result_name, selections_result, parent_object, return_type, return_type_non_null)
|
439
472
|
end
|
440
473
|
end
|
441
474
|
end
|
442
475
|
|
443
|
-
def evaluate_selection_with_args(arguments, field_defn,
|
444
|
-
|
445
|
-
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|
|
476
|
+
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
|
477
|
+
after_lazy(arguments, owner: owner_type, field: field_defn, ast_node: ast_node, owner_object: object, arguments: arguments, result_name: result_name, result: selection_result) do |resolved_arguments|
|
446
478
|
if resolved_arguments.is_a?(GraphQL::ExecutionError) || resolved_arguments.is_a?(GraphQL::UnauthorizedError)
|
447
|
-
continue_value(
|
479
|
+
continue_value(resolved_arguments, owner_type, field_defn, return_type_non_null, ast_node, result_name, selection_result)
|
448
480
|
next
|
449
481
|
end
|
450
482
|
|
451
|
-
kwarg_arguments = if
|
452
|
-
|
453
|
-
|
483
|
+
kwarg_arguments = if field_defn.extras.empty?
|
484
|
+
if resolved_arguments.empty?
|
485
|
+
# We can avoid allocating the `{ Symbol => Object }` hash in this case
|
486
|
+
NO_ARGS
|
487
|
+
else
|
488
|
+
resolved_arguments.keyword_arguments
|
489
|
+
end
|
454
490
|
else
|
455
491
|
# Bundle up the extras, then make a new arguments instance
|
456
492
|
# that includes the extras, too.
|
@@ -460,9 +496,9 @@ module GraphQL
|
|
460
496
|
when :ast_node
|
461
497
|
extra_args[:ast_node] = ast_node
|
462
498
|
when :execution_errors
|
463
|
-
extra_args[:execution_errors] = ExecutionErrors.new(context, ast_node,
|
499
|
+
extra_args[:execution_errors] = ExecutionErrors.new(context, ast_node, current_path)
|
464
500
|
when :path
|
465
|
-
extra_args[:path] =
|
501
|
+
extra_args[:path] = current_path
|
466
502
|
when :lookahead
|
467
503
|
if !field_ast_nodes
|
468
504
|
field_ast_nodes = [ast_node]
|
@@ -489,61 +525,70 @@ module GraphQL
|
|
489
525
|
resolved_arguments.keyword_arguments
|
490
526
|
end
|
491
527
|
|
492
|
-
|
528
|
+
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)
|
529
|
+
end
|
530
|
+
end
|
493
531
|
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
ex_err
|
520
|
-
end
|
532
|
+
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
|
533
|
+
st = get_current_runtime_state
|
534
|
+
st.current_field = field_defn
|
535
|
+
st.current_object = object
|
536
|
+
st.current_arguments = resolved_arguments
|
537
|
+
st.current_result_name = result_name
|
538
|
+
st.current_result = selection_result
|
539
|
+
# Optimize for the case that field is selected only once
|
540
|
+
if field_ast_nodes.nil? || field_ast_nodes.size == 1
|
541
|
+
next_selections = ast_node.selections
|
542
|
+
directives = ast_node.directives
|
543
|
+
else
|
544
|
+
next_selections = []
|
545
|
+
directives = []
|
546
|
+
field_ast_nodes.each { |f|
|
547
|
+
next_selections.concat(f.selections)
|
548
|
+
directives.concat(f.directives)
|
549
|
+
}
|
550
|
+
end
|
551
|
+
|
552
|
+
field_result = call_method_on_directives(:resolve, object, directives) do
|
553
|
+
# Actually call the field resolver and capture the result
|
554
|
+
app_result = begin
|
555
|
+
query.current_trace.execute_field(field: field_defn, ast_node: ast_node, query: query, object: object, arguments: kwarg_arguments) do
|
556
|
+
field_defn.resolve(object, kwarg_arguments, context)
|
521
557
|
end
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
558
|
+
rescue GraphQL::ExecutionError => err
|
559
|
+
err
|
560
|
+
rescue StandardError => err
|
561
|
+
begin
|
562
|
+
query.handle_or_reraise(err)
|
563
|
+
rescue GraphQL::ExecutionError => ex_err
|
564
|
+
ex_err
|
527
565
|
end
|
528
566
|
end
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
Interpreter::Resolve.resolve_all([field_result], @dataloader)
|
535
|
-
else
|
536
|
-
# Return this from `after_lazy` because it might be another lazy that needs to be resolved
|
537
|
-
field_result
|
567
|
+
after_lazy(app_result, owner: owner_type, field: field_defn, ast_node: ast_node, owner_object: object, arguments: resolved_arguments, result_name: result_name, result: selection_result) do |inner_result|
|
568
|
+
continue_value = continue_value(inner_result, owner_type, field_defn, return_type_non_null, ast_node, result_name, selection_result)
|
569
|
+
if HALT != continue_value
|
570
|
+
continue_field(continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, resolved_arguments, result_name, selection_result)
|
571
|
+
end
|
538
572
|
end
|
539
573
|
end
|
574
|
+
|
575
|
+
# If this field is a root mutation field, immediately resolve
|
576
|
+
# all of its child fields before moving on to the next root mutation field.
|
577
|
+
# (Subselections of this mutation will still be resolved level-by-level.)
|
578
|
+
if is_eager_field
|
579
|
+
Interpreter::Resolve.resolve_all([field_result], @dataloader)
|
580
|
+
else
|
581
|
+
# Return this from `after_lazy` because it might be another lazy that needs to be resolved
|
582
|
+
field_result
|
583
|
+
end
|
540
584
|
end
|
541
585
|
|
586
|
+
|
542
587
|
def dead_result?(selection_result)
|
543
|
-
selection_result.graphql_dead || ((parent = selection_result.graphql_parent) && parent.graphql_dead)
|
588
|
+
selection_result.graphql_dead # || ((parent = selection_result.graphql_parent) && parent.graphql_dead)
|
544
589
|
end
|
545
590
|
|
546
|
-
def set_result(selection_result, result_name, value)
|
591
|
+
def set_result(selection_result, result_name, value, is_child_result)
|
547
592
|
if !dead_result?(selection_result)
|
548
593
|
if value.nil? &&
|
549
594
|
( # there are two conditions under which `nil` is not allowed in the response:
|
@@ -563,11 +608,13 @@ module GraphQL
|
|
563
608
|
if parent.nil? # This is a top-level result hash
|
564
609
|
@response = nil
|
565
610
|
else
|
566
|
-
set_result(parent, name_in_parent, nil)
|
611
|
+
set_result(parent, name_in_parent, nil, false)
|
567
612
|
set_graphql_dead(selection_result)
|
568
613
|
end
|
614
|
+
elsif is_child_result
|
615
|
+
selection_result.set_child_result(result_name, value)
|
569
616
|
else
|
570
|
-
selection_result
|
617
|
+
selection_result.set_leaf(result_name, value)
|
571
618
|
end
|
572
619
|
end
|
573
620
|
end
|
@@ -587,18 +634,29 @@ module GraphQL
|
|
587
634
|
end
|
588
635
|
end
|
589
636
|
|
637
|
+
def current_path
|
638
|
+
st = get_current_runtime_state
|
639
|
+
result = st.current_result
|
640
|
+
path = result && result.path
|
641
|
+
if path && (rn = st.current_result_name)
|
642
|
+
path = path.dup
|
643
|
+
path.push(rn)
|
644
|
+
end
|
645
|
+
path
|
646
|
+
end
|
647
|
+
|
590
648
|
HALT = Object.new
|
591
|
-
def continue_value(
|
649
|
+
def continue_value(value, parent_type, field, is_non_null, ast_node, result_name, selection_result) # rubocop:disable Metrics/ParameterLists
|
592
650
|
case value
|
593
651
|
when nil
|
594
652
|
if is_non_null
|
595
|
-
set_result(selection_result, result_name, nil) do
|
653
|
+
set_result(selection_result, result_name, nil, false) do
|
596
654
|
# This block is called if `result_name` is not dead. (Maybe a previous invalid nil caused it be marked dead.)
|
597
655
|
err = parent_type::InvalidNullError.new(parent_type, field, value)
|
598
656
|
schema.type_error(err, context)
|
599
657
|
end
|
600
658
|
else
|
601
|
-
set_result(selection_result, result_name, nil)
|
659
|
+
set_result(selection_result, result_name, nil, false)
|
602
660
|
end
|
603
661
|
HALT
|
604
662
|
when GraphQL::Error
|
@@ -607,14 +665,24 @@ module GraphQL
|
|
607
665
|
# every time.
|
608
666
|
if value.is_a?(GraphQL::ExecutionError)
|
609
667
|
if selection_result.nil? || !dead_result?(selection_result)
|
610
|
-
value.path ||=
|
668
|
+
value.path ||= current_path
|
611
669
|
value.ast_node ||= ast_node
|
612
670
|
context.errors << value
|
613
671
|
if selection_result
|
614
|
-
set_result(selection_result, result_name, nil)
|
672
|
+
set_result(selection_result, result_name, nil, false)
|
615
673
|
end
|
616
674
|
end
|
617
675
|
HALT
|
676
|
+
elsif value.is_a?(GraphQL::UnauthorizedFieldError)
|
677
|
+
value.field ||= field
|
678
|
+
# this hook might raise & crash, or it might return
|
679
|
+
# a replacement value
|
680
|
+
next_value = begin
|
681
|
+
schema.unauthorized_field(value)
|
682
|
+
rescue GraphQL::ExecutionError => err
|
683
|
+
err
|
684
|
+
end
|
685
|
+
continue_value(next_value, parent_type, field, is_non_null, ast_node, result_name, selection_result)
|
618
686
|
elsif value.is_a?(GraphQL::UnauthorizedError)
|
619
687
|
# this hook might raise & crash, or it might return
|
620
688
|
# a replacement value
|
@@ -623,7 +691,7 @@ module GraphQL
|
|
623
691
|
rescue GraphQL::ExecutionError => err
|
624
692
|
err
|
625
693
|
end
|
626
|
-
continue_value(
|
694
|
+
continue_value(next_value, parent_type, field, is_non_null, ast_node, result_name, selection_result)
|
627
695
|
elsif GraphQL::Execution::SKIP == value
|
628
696
|
# It's possible a lazy was already written here
|
629
697
|
case selection_result
|
@@ -649,15 +717,15 @@ module GraphQL
|
|
649
717
|
if selection_result.nil? || !dead_result?(selection_result)
|
650
718
|
value.each_with_index do |error, index|
|
651
719
|
error.ast_node ||= ast_node
|
652
|
-
error.path ||=
|
720
|
+
error.path ||= current_path + (list_type_at_all ? [index] : [])
|
653
721
|
context.errors << error
|
654
722
|
end
|
655
723
|
if selection_result
|
656
724
|
if list_type_at_all
|
657
725
|
result_without_errors = value.map { |v| v.is_a?(GraphQL::ExecutionError) ? nil : v }
|
658
|
-
set_result(selection_result, result_name, result_without_errors)
|
726
|
+
set_result(selection_result, result_name, result_without_errors, false)
|
659
727
|
else
|
660
|
-
set_result(selection_result, result_name, nil)
|
728
|
+
set_result(selection_result, result_name, nil, false)
|
661
729
|
end
|
662
730
|
end
|
663
731
|
end
|
@@ -667,7 +735,7 @@ module GraphQL
|
|
667
735
|
end
|
668
736
|
when GraphQL::Execution::Interpreter::RawValue
|
669
737
|
# Write raw value directly to the response without resolving nested objects
|
670
|
-
set_result(selection_result, result_name, value.resolve)
|
738
|
+
set_result(selection_result, result_name, value.resolve, false)
|
671
739
|
HALT
|
672
740
|
else
|
673
741
|
value
|
@@ -682,7 +750,7 @@ module GraphQL
|
|
682
750
|
# Location information from `path` and `ast_node`.
|
683
751
|
#
|
684
752
|
# @return [Lazy, Array, Hash, Object] Lazy, Array, and Hash are all traversed to resolve lazy values later
|
685
|
-
def continue_field(
|
753
|
+
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
|
686
754
|
if current_type.non_null?
|
687
755
|
current_type = current_type.of_type
|
688
756
|
is_non_null = true
|
@@ -695,11 +763,11 @@ module GraphQL
|
|
695
763
|
rescue StandardError => err
|
696
764
|
schema.handle_or_reraise(context, err)
|
697
765
|
end
|
698
|
-
set_result(selection_result, result_name, r)
|
766
|
+
set_result(selection_result, result_name, r, false)
|
699
767
|
r
|
700
768
|
when "UNION", "INTERFACE"
|
701
|
-
resolved_type_or_lazy = resolve_type(current_type, value
|
702
|
-
after_lazy(resolved_type_or_lazy, owner: current_type,
|
769
|
+
resolved_type_or_lazy = resolve_type(current_type, value)
|
770
|
+
after_lazy(resolved_type_or_lazy, owner: current_type, 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|
|
703
771
|
if resolved_type_result.is_a?(Array) && resolved_type_result.length == 2
|
704
772
|
resolved_type, resolved_value = resolved_type_result
|
705
773
|
else
|
@@ -713,10 +781,10 @@ module GraphQL
|
|
713
781
|
err_class = current_type::UnresolvedTypeError
|
714
782
|
type_error = err_class.new(resolved_value, field, parent_type, resolved_type, possible_types)
|
715
783
|
schema.type_error(type_error, context)
|
716
|
-
set_result(selection_result, result_name, nil)
|
784
|
+
set_result(selection_result, result_name, nil, false)
|
717
785
|
nil
|
718
786
|
else
|
719
|
-
continue_field(
|
787
|
+
continue_field(resolved_value, owner_type, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result)
|
720
788
|
end
|
721
789
|
end
|
722
790
|
when "OBJECT"
|
@@ -725,11 +793,11 @@ module GraphQL
|
|
725
793
|
rescue GraphQL::ExecutionError => err
|
726
794
|
err
|
727
795
|
end
|
728
|
-
after_lazy(object_proxy, owner: current_type,
|
729
|
-
continue_value = continue_value(
|
796
|
+
after_lazy(object_proxy, owner: current_type, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result) do |inner_object|
|
797
|
+
continue_value = continue_value(inner_object, owner_type, field, is_non_null, ast_node, result_name, selection_result)
|
730
798
|
if HALT != continue_value
|
731
799
|
response_hash = GraphQLResultHash.new(result_name, selection_result)
|
732
|
-
set_result(selection_result, result_name, response_hash)
|
800
|
+
set_result(selection_result, result_name, response_hash, true)
|
733
801
|
gathered_selections = gather_selections(continue_value, current_type, next_selections)
|
734
802
|
# There are two possibilities for `gathered_selections`:
|
735
803
|
# 1. All selections of this object should be evaluated together (there are no runtime directives modifying execution).
|
@@ -747,10 +815,15 @@ module GraphQL
|
|
747
815
|
this_result = response_hash
|
748
816
|
final_result = nil
|
749
817
|
end
|
750
|
-
|
818
|
+
# reset this mutable state
|
819
|
+
# Unset `result_name` here because it's already included in the new response hash
|
820
|
+
st = get_current_runtime_state
|
821
|
+
st.current_object = continue_value
|
822
|
+
st.current_result_name = nil
|
823
|
+
st.current_result = this_result
|
824
|
+
|
751
825
|
call_method_on_directives(:resolve, continue_value, selections.graphql_directives) do
|
752
826
|
evaluate_selections(
|
753
|
-
path,
|
754
827
|
continue_value,
|
755
828
|
current_type,
|
756
829
|
false,
|
@@ -768,43 +841,30 @@ module GraphQL
|
|
768
841
|
inner_type = current_type.of_type
|
769
842
|
# This is true for objects, unions, and interfaces
|
770
843
|
use_dataloader_job = !inner_type.unwrap.kind.input?
|
844
|
+
inner_type_non_null = inner_type.non_null?
|
771
845
|
response_list = GraphQLResultArray.new(result_name, selection_result)
|
772
|
-
response_list.graphql_non_null_list_items =
|
773
|
-
set_result(selection_result, result_name, response_list)
|
774
|
-
result_was_set = false
|
846
|
+
response_list.graphql_non_null_list_items = inner_type_non_null
|
847
|
+
set_result(selection_result, result_name, response_list, true)
|
775
848
|
idx = 0
|
776
849
|
list_value = begin
|
777
850
|
value.each do |inner_value|
|
778
|
-
break if dead_result?(response_list)
|
779
|
-
if !result_was_set
|
780
|
-
# Don't set the result unless `.each` is successful
|
781
|
-
set_result(selection_result, result_name, response_list)
|
782
|
-
result_was_set = true
|
783
|
-
end
|
784
|
-
next_path = path + [idx]
|
785
851
|
this_idx = idx
|
786
|
-
next_path.freeze
|
787
852
|
idx += 1
|
788
853
|
if use_dataloader_job
|
789
854
|
@dataloader.append_job do
|
790
|
-
resolve_list_item(inner_value, inner_type,
|
855
|
+
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)
|
791
856
|
end
|
792
857
|
else
|
793
|
-
resolve_list_item(inner_value, inner_type,
|
858
|
+
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)
|
794
859
|
end
|
795
860
|
end
|
796
|
-
# Maybe the list was empty and the block was never called.
|
797
|
-
if !result_was_set
|
798
|
-
set_result(selection_result, result_name, response_list)
|
799
|
-
result_was_set = true
|
800
|
-
end
|
801
861
|
|
802
862
|
response_list
|
803
863
|
rescue NoMethodError => err
|
804
864
|
# Ruby 2.2 doesn't have NoMethodError#receiver, can't check that one in this case. (It's been EOL since 2017.)
|
805
865
|
if err.name == :each && (err.respond_to?(:receiver) ? err.receiver == value : true)
|
806
866
|
# This happens when the GraphQL schema doesn't match the implementation. Help the dev debug.
|
807
|
-
raise ListResultFailedError.new(value: value, field: field, path:
|
867
|
+
raise ListResultFailedError.new(value: value, field: field, path: current_path)
|
808
868
|
else
|
809
869
|
# This was some other NoMethodError -- let it bubble to reveal the real error.
|
810
870
|
raise
|
@@ -819,20 +879,22 @@ module GraphQL
|
|
819
879
|
end
|
820
880
|
end
|
821
881
|
|
822
|
-
continue_value(
|
882
|
+
continue_value(list_value, owner_type, field, inner_type.non_null?, ast_node, result_name, selection_result)
|
823
883
|
else
|
824
884
|
raise "Invariant: Unhandled type kind #{current_type.kind} (#{current_type})"
|
825
885
|
end
|
826
886
|
end
|
827
887
|
|
828
|
-
def resolve_list_item(inner_value, inner_type,
|
829
|
-
|
888
|
+
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
|
889
|
+
st = get_current_runtime_state
|
890
|
+
st.current_result_name = this_idx
|
891
|
+
st.current_result = response_list
|
830
892
|
call_method_on_directives(:resolve_each, owner_object, ast_node.directives) do
|
831
893
|
# This will update `response_list` with the lazy
|
832
|
-
after_lazy(inner_value, owner: inner_type,
|
833
|
-
continue_value = continue_value(
|
894
|
+
after_lazy(inner_value, owner: inner_type, ast_node: ast_node, field: field, owner_object: owner_object, arguments: arguments, result_name: this_idx, result: response_list) do |inner_inner_value|
|
895
|
+
continue_value = continue_value(inner_inner_value, owner_type, field, inner_type_non_null, ast_node, this_idx, response_list)
|
834
896
|
if HALT != continue_value
|
835
|
-
continue_field(
|
897
|
+
continue_field(continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments, this_idx, response_list)
|
836
898
|
end
|
837
899
|
end
|
838
900
|
end
|
@@ -851,7 +913,6 @@ module GraphQL
|
|
851
913
|
dir_defn = @schema_directives.fetch(dir_node.name)
|
852
914
|
raw_dir_args = arguments(nil, dir_defn, dir_node)
|
853
915
|
dir_args = continue_value(
|
854
|
-
@context[:current_path], # path
|
855
916
|
raw_dir_args, # value
|
856
917
|
dir_defn, # parent_type
|
857
918
|
nil, # field
|
@@ -883,37 +944,30 @@ module GraphQL
|
|
883
944
|
true
|
884
945
|
end
|
885
946
|
|
886
|
-
def
|
887
|
-
|
888
|
-
if object
|
889
|
-
ti[:current_object] = object
|
890
|
-
end
|
891
|
-
if field
|
892
|
-
ti[:current_field] = field
|
893
|
-
end
|
894
|
-
if arguments
|
895
|
-
ti[:current_arguments] = arguments
|
896
|
-
end
|
897
|
-
if path
|
898
|
-
ti[:current_path] = path
|
899
|
-
end
|
947
|
+
def get_current_runtime_state
|
948
|
+
Thread.current[:__graphql_runtime_info] ||= CurrentState.new
|
900
949
|
end
|
901
950
|
|
902
951
|
# @param obj [Object] Some user-returned value that may want to be batched
|
903
|
-
# @param path [Array<String>]
|
904
952
|
# @param field [GraphQL::Schema::Field]
|
905
953
|
# @param eager [Boolean] Set to `true` for mutation root fields only
|
906
954
|
# @param trace [Boolean] If `false`, don't wrap this with field tracing
|
907
955
|
# @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.
|
908
|
-
def after_lazy(lazy_obj, owner:, field:,
|
956
|
+
def after_lazy(lazy_obj, owner:, field:, owner_object:, arguments:, ast_node:, result:, result_name:, eager: false, trace: true, &block)
|
909
957
|
if lazy?(lazy_obj)
|
910
|
-
|
911
|
-
|
958
|
+
orig_result = result
|
959
|
+
lazy = GraphQL::Execution::Lazy.new(field: field) do
|
960
|
+
st = get_current_runtime_state
|
961
|
+
st.current_object = owner_object
|
962
|
+
st.current_field = field
|
963
|
+
st.current_arguments = arguments
|
964
|
+
st.current_result_name = result_name
|
965
|
+
st.current_result = orig_result
|
912
966
|
# Wrap the execution of _this_ method with tracing,
|
913
967
|
# but don't wrap the continuation below
|
914
968
|
inner_obj = begin
|
915
969
|
if trace
|
916
|
-
query.
|
970
|
+
query.current_trace.execute_field_lazy(field: field, query: query, object: owner_object, arguments: arguments, ast_node: ast_node) do
|
917
971
|
schema.sync_lazy(lazy_obj)
|
918
972
|
end
|
919
973
|
else
|
@@ -934,11 +988,17 @@ module GraphQL
|
|
934
988
|
if eager
|
935
989
|
lazy.value
|
936
990
|
else
|
937
|
-
set_result(result, result_name, lazy)
|
991
|
+
set_result(result, result_name, lazy, false)
|
992
|
+
current_depth = 0
|
993
|
+
while result
|
994
|
+
current_depth += 1
|
995
|
+
result = result.graphql_parent
|
996
|
+
end
|
997
|
+
@lazies_at_depth[current_depth] << lazy
|
938
998
|
lazy
|
939
999
|
end
|
940
1000
|
else
|
941
|
-
|
1001
|
+
# Don't need to reset state here because it _wasn't_ lazy.
|
942
1002
|
yield(lazy_obj)
|
943
1003
|
end
|
944
1004
|
end
|
@@ -952,25 +1012,18 @@ module GraphQL
|
|
952
1012
|
end
|
953
1013
|
end
|
954
1014
|
|
955
|
-
|
956
|
-
|
957
|
-
def set_interpreter_context(key, value)
|
958
|
-
thread_info[key] = value
|
959
|
-
end
|
960
|
-
|
961
|
-
def delete_interpreter_context(key)
|
962
|
-
(ti = thread_info) && ti.delete(key)
|
1015
|
+
def delete_all_interpreter_context
|
1016
|
+
Thread.current[:__graphql_runtime_info] = nil
|
963
1017
|
end
|
964
1018
|
|
965
|
-
def resolve_type(type, value
|
966
|
-
|
967
|
-
resolved_type, resolved_value = query.trace("resolve_type", trace_payload) do
|
1019
|
+
def resolve_type(type, value)
|
1020
|
+
resolved_type, resolved_value = query.current_trace.resolve_type(query: query, type: type, object: value) do
|
968
1021
|
query.resolve_type(type, value)
|
969
1022
|
end
|
970
1023
|
|
971
1024
|
if lazy?(resolved_type)
|
972
1025
|
GraphQL::Execution::Lazy.new do
|
973
|
-
query.
|
1026
|
+
query.current_trace.resolve_type_lazy(query: query, type: type, object: value) do
|
974
1027
|
schema.sync_lazy(resolved_type)
|
975
1028
|
end
|
976
1029
|
end
|