graphql 2.0.17.2 → 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.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 +254 -211
- data/lib/graphql/execution/interpreter.rb +9 -14
- data/lib/graphql/execution/lazy.rb +2 -4
- data/lib/graphql/execution/multiplex.rb +2 -1
- data/lib/graphql/filter.rb +7 -2
- 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 +27 -9
- data/lib/graphql/language/parser.rb +509 -491
- data/lib/graphql/language/parser.y +43 -38
- 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 -9
- data/lib/graphql/schema/build_from_definition.rb +15 -3
- data/lib/graphql/schema/enum_value.rb +2 -5
- data/lib/graphql/schema/field.rb +44 -31
- data/lib/graphql/schema/field_extension.rb +1 -4
- data/lib/graphql/schema/find_inherited_value.rb +2 -7
- data/lib/graphql/schema/member/base_dsl_methods.rb +13 -11
- data/lib/graphql/schema/member/has_arguments.rb +1 -1
- 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 +87 -37
- data/lib/graphql/schema/member/has_validators.rb +2 -2
- data/lib/graphql/schema/member/relay_shortcuts.rb +19 -0
- data/lib/graphql/schema/member/type_system_helpers.rb +1 -1
- 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 +11 -2
- data/lib/graphql/schema.rb +72 -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/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 -2
- data/lib/graphql/types/relay/edge_behaviors.rb +16 -2
- 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/version.rb +1 -1
- data/lib/graphql.rb +16 -9
- metadata +33 -8
- 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
|
@@ -412,17 +437,19 @@ module GraphQL
|
|
412
437
|
|
413
438
|
return_type = field_defn.type
|
414
439
|
|
415
|
-
next_path = path + [result_name]
|
416
|
-
next_path.freeze
|
417
|
-
|
418
440
|
# This seems janky, but we need to know
|
419
441
|
# the field's return type at this path in order
|
420
442
|
# to propagate `null`
|
421
|
-
|
443
|
+
return_type_non_null = return_type.non_null?
|
444
|
+
if return_type_non_null
|
422
445
|
(selections_result.graphql_non_null_field_names ||= []).push(result_name)
|
423
446
|
end
|
424
447
|
# Set this before calling `run_with_directives`, so that the directive can have the latest path
|
425
|
-
|
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
|
+
|
426
453
|
object = owner_object
|
427
454
|
|
428
455
|
if is_introspection
|
@@ -432,25 +459,34 @@ module GraphQL
|
|
432
459
|
total_args_count = field_defn.arguments(context).size
|
433
460
|
if total_args_count == 0
|
434
461
|
resolved_arguments = GraphQL::Execution::Interpreter::Arguments::EMPTY
|
435
|
-
|
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
|
436
469
|
else
|
437
|
-
# TODO remove all arguments(...) usages?
|
438
470
|
@query.arguments_cache.dataload_for(ast_node, field_defn, object) do |resolved_arguments|
|
439
|
-
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)
|
440
472
|
end
|
441
473
|
end
|
442
474
|
end
|
443
475
|
|
444
|
-
def evaluate_selection_with_args(arguments, field_defn,
|
445
|
-
after_lazy(arguments, owner: owner_type, field: field_defn,
|
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,11 +665,11 @@ 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
|
@@ -624,7 +682,7 @@ module GraphQL
|
|
624
682
|
rescue GraphQL::ExecutionError => err
|
625
683
|
err
|
626
684
|
end
|
627
|
-
continue_value(
|
685
|
+
continue_value(next_value, parent_type, field, is_non_null, ast_node, result_name, selection_result)
|
628
686
|
elsif value.is_a?(GraphQL::UnauthorizedError)
|
629
687
|
# this hook might raise & crash, or it might return
|
630
688
|
# a replacement value
|
@@ -633,7 +691,7 @@ module GraphQL
|
|
633
691
|
rescue GraphQL::ExecutionError => err
|
634
692
|
err
|
635
693
|
end
|
636
|
-
continue_value(
|
694
|
+
continue_value(next_value, parent_type, field, is_non_null, ast_node, result_name, selection_result)
|
637
695
|
elsif GraphQL::Execution::SKIP == value
|
638
696
|
# It's possible a lazy was already written here
|
639
697
|
case selection_result
|
@@ -659,15 +717,15 @@ module GraphQL
|
|
659
717
|
if selection_result.nil? || !dead_result?(selection_result)
|
660
718
|
value.each_with_index do |error, index|
|
661
719
|
error.ast_node ||= ast_node
|
662
|
-
error.path ||=
|
720
|
+
error.path ||= current_path + (list_type_at_all ? [index] : [])
|
663
721
|
context.errors << error
|
664
722
|
end
|
665
723
|
if selection_result
|
666
724
|
if list_type_at_all
|
667
725
|
result_without_errors = value.map { |v| v.is_a?(GraphQL::ExecutionError) ? nil : v }
|
668
|
-
set_result(selection_result, result_name, result_without_errors)
|
726
|
+
set_result(selection_result, result_name, result_without_errors, false)
|
669
727
|
else
|
670
|
-
set_result(selection_result, result_name, nil)
|
728
|
+
set_result(selection_result, result_name, nil, false)
|
671
729
|
end
|
672
730
|
end
|
673
731
|
end
|
@@ -677,7 +735,7 @@ module GraphQL
|
|
677
735
|
end
|
678
736
|
when GraphQL::Execution::Interpreter::RawValue
|
679
737
|
# Write raw value directly to the response without resolving nested objects
|
680
|
-
set_result(selection_result, result_name, value.resolve)
|
738
|
+
set_result(selection_result, result_name, value.resolve, false)
|
681
739
|
HALT
|
682
740
|
else
|
683
741
|
value
|
@@ -692,7 +750,7 @@ module GraphQL
|
|
692
750
|
# Location information from `path` and `ast_node`.
|
693
751
|
#
|
694
752
|
# @return [Lazy, Array, Hash, Object] Lazy, Array, and Hash are all traversed to resolve lazy values later
|
695
|
-
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
|
696
754
|
if current_type.non_null?
|
697
755
|
current_type = current_type.of_type
|
698
756
|
is_non_null = true
|
@@ -705,11 +763,11 @@ module GraphQL
|
|
705
763
|
rescue StandardError => err
|
706
764
|
schema.handle_or_reraise(context, err)
|
707
765
|
end
|
708
|
-
set_result(selection_result, result_name, r)
|
766
|
+
set_result(selection_result, result_name, r, false)
|
709
767
|
r
|
710
768
|
when "UNION", "INTERFACE"
|
711
|
-
resolved_type_or_lazy = resolve_type(current_type, value
|
712
|
-
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|
|
713
771
|
if resolved_type_result.is_a?(Array) && resolved_type_result.length == 2
|
714
772
|
resolved_type, resolved_value = resolved_type_result
|
715
773
|
else
|
@@ -723,10 +781,10 @@ module GraphQL
|
|
723
781
|
err_class = current_type::UnresolvedTypeError
|
724
782
|
type_error = err_class.new(resolved_value, field, parent_type, resolved_type, possible_types)
|
725
783
|
schema.type_error(type_error, context)
|
726
|
-
set_result(selection_result, result_name, nil)
|
784
|
+
set_result(selection_result, result_name, nil, false)
|
727
785
|
nil
|
728
786
|
else
|
729
|
-
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)
|
730
788
|
end
|
731
789
|
end
|
732
790
|
when "OBJECT"
|
@@ -735,11 +793,11 @@ module GraphQL
|
|
735
793
|
rescue GraphQL::ExecutionError => err
|
736
794
|
err
|
737
795
|
end
|
738
|
-
after_lazy(object_proxy, owner: current_type,
|
739
|
-
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)
|
740
798
|
if HALT != continue_value
|
741
799
|
response_hash = GraphQLResultHash.new(result_name, selection_result)
|
742
|
-
set_result(selection_result, result_name, response_hash)
|
800
|
+
set_result(selection_result, result_name, response_hash, true)
|
743
801
|
gathered_selections = gather_selections(continue_value, current_type, next_selections)
|
744
802
|
# There are two possibilities for `gathered_selections`:
|
745
803
|
# 1. All selections of this object should be evaluated together (there are no runtime directives modifying execution).
|
@@ -757,10 +815,15 @@ module GraphQL
|
|
757
815
|
this_result = response_hash
|
758
816
|
final_result = nil
|
759
817
|
end
|
760
|
-
|
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
|
+
|
761
825
|
call_method_on_directives(:resolve, continue_value, selections.graphql_directives) do
|
762
826
|
evaluate_selections(
|
763
|
-
path,
|
764
827
|
continue_value,
|
765
828
|
current_type,
|
766
829
|
false,
|
@@ -778,43 +841,30 @@ module GraphQL
|
|
778
841
|
inner_type = current_type.of_type
|
779
842
|
# This is true for objects, unions, and interfaces
|
780
843
|
use_dataloader_job = !inner_type.unwrap.kind.input?
|
844
|
+
inner_type_non_null = inner_type.non_null?
|
781
845
|
response_list = GraphQLResultArray.new(result_name, selection_result)
|
782
|
-
response_list.graphql_non_null_list_items =
|
783
|
-
set_result(selection_result, result_name, response_list)
|
784
|
-
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)
|
785
848
|
idx = 0
|
786
849
|
list_value = begin
|
787
850
|
value.each do |inner_value|
|
788
|
-
break if dead_result?(response_list)
|
789
|
-
if !result_was_set
|
790
|
-
# Don't set the result unless `.each` is successful
|
791
|
-
set_result(selection_result, result_name, response_list)
|
792
|
-
result_was_set = true
|
793
|
-
end
|
794
|
-
next_path = path + [idx]
|
795
851
|
this_idx = idx
|
796
|
-
next_path.freeze
|
797
852
|
idx += 1
|
798
853
|
if use_dataloader_job
|
799
854
|
@dataloader.append_job do
|
800
|
-
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)
|
801
856
|
end
|
802
857
|
else
|
803
|
-
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)
|
804
859
|
end
|
805
860
|
end
|
806
|
-
# Maybe the list was empty and the block was never called.
|
807
|
-
if !result_was_set
|
808
|
-
set_result(selection_result, result_name, response_list)
|
809
|
-
result_was_set = true
|
810
|
-
end
|
811
861
|
|
812
862
|
response_list
|
813
863
|
rescue NoMethodError => err
|
814
864
|
# Ruby 2.2 doesn't have NoMethodError#receiver, can't check that one in this case. (It's been EOL since 2017.)
|
815
865
|
if err.name == :each && (err.respond_to?(:receiver) ? err.receiver == value : true)
|
816
866
|
# This happens when the GraphQL schema doesn't match the implementation. Help the dev debug.
|
817
|
-
raise ListResultFailedError.new(value: value, field: field, path:
|
867
|
+
raise ListResultFailedError.new(value: value, field: field, path: current_path)
|
818
868
|
else
|
819
869
|
# This was some other NoMethodError -- let it bubble to reveal the real error.
|
820
870
|
raise
|
@@ -829,20 +879,22 @@ module GraphQL
|
|
829
879
|
end
|
830
880
|
end
|
831
881
|
|
832
|
-
continue_value(
|
882
|
+
continue_value(list_value, owner_type, field, inner_type.non_null?, ast_node, result_name, selection_result)
|
833
883
|
else
|
834
884
|
raise "Invariant: Unhandled type kind #{current_type.kind} (#{current_type})"
|
835
885
|
end
|
836
886
|
end
|
837
887
|
|
838
|
-
def resolve_list_item(inner_value, inner_type,
|
839
|
-
|
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
|
840
892
|
call_method_on_directives(:resolve_each, owner_object, ast_node.directives) do
|
841
893
|
# This will update `response_list` with the lazy
|
842
|
-
after_lazy(inner_value, owner: inner_type,
|
843
|
-
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)
|
844
896
|
if HALT != continue_value
|
845
|
-
continue_field(
|
897
|
+
continue_field(continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments, this_idx, response_list)
|
846
898
|
end
|
847
899
|
end
|
848
900
|
end
|
@@ -861,7 +913,6 @@ module GraphQL
|
|
861
913
|
dir_defn = @schema_directives.fetch(dir_node.name)
|
862
914
|
raw_dir_args = arguments(nil, dir_defn, dir_node)
|
863
915
|
dir_args = continue_value(
|
864
|
-
@context[:current_path], # path
|
865
916
|
raw_dir_args, # value
|
866
917
|
dir_defn, # parent_type
|
867
918
|
nil, # field
|
@@ -893,37 +944,30 @@ module GraphQL
|
|
893
944
|
true
|
894
945
|
end
|
895
946
|
|
896
|
-
def
|
897
|
-
|
898
|
-
if object
|
899
|
-
ti[:current_object] = object
|
900
|
-
end
|
901
|
-
if field
|
902
|
-
ti[:current_field] = field
|
903
|
-
end
|
904
|
-
if arguments
|
905
|
-
ti[:current_arguments] = arguments
|
906
|
-
end
|
907
|
-
if path
|
908
|
-
ti[:current_path] = path
|
909
|
-
end
|
947
|
+
def get_current_runtime_state
|
948
|
+
Thread.current[:__graphql_runtime_info] ||= CurrentState.new
|
910
949
|
end
|
911
950
|
|
912
951
|
# @param obj [Object] Some user-returned value that may want to be batched
|
913
|
-
# @param path [Array<String>]
|
914
952
|
# @param field [GraphQL::Schema::Field]
|
915
953
|
# @param eager [Boolean] Set to `true` for mutation root fields only
|
916
954
|
# @param trace [Boolean] If `false`, don't wrap this with field tracing
|
917
955
|
# @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.
|
918
|
-
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)
|
919
957
|
if lazy?(lazy_obj)
|
920
|
-
|
921
|
-
|
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
|
922
966
|
# Wrap the execution of _this_ method with tracing,
|
923
967
|
# but don't wrap the continuation below
|
924
968
|
inner_obj = begin
|
925
969
|
if trace
|
926
|
-
query.
|
970
|
+
query.current_trace.execute_field_lazy(field: field, query: query, object: owner_object, arguments: arguments, ast_node: ast_node) do
|
927
971
|
schema.sync_lazy(lazy_obj)
|
928
972
|
end
|
929
973
|
else
|
@@ -944,11 +988,17 @@ module GraphQL
|
|
944
988
|
if eager
|
945
989
|
lazy.value
|
946
990
|
else
|
947
|
-
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
|
948
998
|
lazy
|
949
999
|
end
|
950
1000
|
else
|
951
|
-
|
1001
|
+
# Don't need to reset state here because it _wasn't_ lazy.
|
952
1002
|
yield(lazy_obj)
|
953
1003
|
end
|
954
1004
|
end
|
@@ -962,25 +1012,18 @@ module GraphQL
|
|
962
1012
|
end
|
963
1013
|
end
|
964
1014
|
|
965
|
-
|
966
|
-
|
967
|
-
def set_interpreter_context(key, value)
|
968
|
-
thread_info[key] = value
|
969
|
-
end
|
970
|
-
|
971
|
-
def delete_interpreter_context(key)
|
972
|
-
(ti = thread_info) && ti.delete(key)
|
1015
|
+
def delete_all_interpreter_context
|
1016
|
+
Thread.current[:__graphql_runtime_info] = nil
|
973
1017
|
end
|
974
1018
|
|
975
|
-
def resolve_type(type, value
|
976
|
-
|
977
|
-
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
|
978
1021
|
query.resolve_type(type, value)
|
979
1022
|
end
|
980
1023
|
|
981
1024
|
if lazy?(resolved_type)
|
982
1025
|
GraphQL::Execution::Lazy.new do
|
983
|
-
query.
|
1026
|
+
query.current_trace.resolve_type_lazy(query: query, type: type, object: value) do
|
984
1027
|
schema.sync_lazy(resolved_type)
|
985
1028
|
end
|
986
1029
|
end
|