graphql 2.0.21 → 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/backtrace.rb +0 -4
- data/lib/graphql/dataloader/source.rb +69 -45
- data/lib/graphql/dataloader.rb +4 -4
- data/lib/graphql/execution/interpreter/arguments_cache.rb +31 -30
- data/lib/graphql/execution/interpreter/runtime.rb +122 -101
- data/lib/graphql/execution/interpreter.rb +1 -2
- data/lib/graphql/execution/lookahead.rb +1 -1
- data/lib/graphql/filter.rb +2 -1
- data/lib/graphql/introspection/entry_points.rb +1 -1
- data/lib/graphql/language/document_from_schema_definition.rb +16 -9
- data/lib/graphql/language/lexer.rb +89 -57
- data/lib/graphql/language/nodes.rb +3 -0
- data/lib/graphql/language/parser.rb +706 -691
- data/lib/graphql/language/parser.y +1 -0
- data/lib/graphql/language/printer.rb +28 -14
- data/lib/graphql/language/visitor.rb +64 -61
- data/lib/graphql/query/context.rb +16 -7
- data/lib/graphql/query/null_context.rb +8 -18
- data/lib/graphql/query/validation_pipeline.rb +2 -1
- data/lib/graphql/query.rb +37 -12
- data/lib/graphql/schema/addition.rb +38 -12
- data/lib/graphql/schema/always_visible.rb +10 -0
- data/lib/graphql/schema/argument.rb +8 -10
- data/lib/graphql/schema/build_from_definition.rb +8 -7
- data/lib/graphql/schema/directive.rb +1 -1
- data/lib/graphql/schema/enum_value.rb +2 -2
- data/lib/graphql/schema/field/connection_extension.rb +1 -1
- data/lib/graphql/schema/field.rb +26 -15
- data/lib/graphql/schema/input_object.rb +9 -7
- data/lib/graphql/schema/interface.rb +5 -1
- data/lib/graphql/schema/introspection_system.rb +1 -1
- data/lib/graphql/schema/member/build_type.rb +10 -2
- data/lib/graphql/schema/member/has_arguments.rb +9 -7
- data/lib/graphql/schema/member/has_directives.rb +1 -1
- data/lib/graphql/schema/member/has_fields.rb +1 -1
- data/lib/graphql/schema/member/has_interfaces.rb +1 -1
- data/lib/graphql/schema/member/type_system_helpers.rb +1 -1
- data/lib/graphql/schema/object.rb +6 -1
- data/lib/graphql/schema/printer.rb +3 -1
- data/lib/graphql/schema/relay_classic_mutation.rb +1 -1
- data/lib/graphql/schema/resolver.rb +12 -10
- data/lib/graphql/schema/timeout.rb +1 -1
- data/lib/graphql/schema/validator.rb +1 -1
- data/lib/graphql/schema/warden.rb +37 -4
- data/lib/graphql/schema.rb +92 -23
- data/lib/graphql/tracing/appoptics_trace.rb +35 -11
- data/lib/graphql/tracing/appsignal_trace.rb +4 -0
- data/lib/graphql/tracing/data_dog_trace.rb +89 -50
- data/lib/graphql/tracing/data_dog_tracing.rb +7 -21
- data/lib/graphql/tracing/legacy_trace.rb +5 -1
- data/lib/graphql/tracing/notifications_trace.rb +7 -0
- data/lib/graphql/tracing/platform_trace.rb +26 -12
- data/lib/graphql/tracing/prometheus_trace.rb +4 -0
- data/lib/graphql/tracing/scout_trace.rb +3 -0
- data/lib/graphql/tracing/statsd_trace.rb +4 -0
- data/lib/graphql/tracing.rb +1 -0
- data/lib/graphql/types/relay/connection_behaviors.rb +1 -1
- data/lib/graphql/types/relay/edge_behaviors.rb +1 -1
- data/lib/graphql/version.rb +1 -1
- data/readme.md +1 -1
- metadata +36 -24
@@ -22,12 +22,13 @@ module GraphQL
|
|
22
22
|
end
|
23
23
|
|
24
24
|
module GraphQLResult
|
25
|
-
def initialize(result_name, parent_result)
|
25
|
+
def initialize(result_name, parent_result, is_non_null_in_parent)
|
26
26
|
@graphql_parent = parent_result
|
27
27
|
if parent_result && parent_result.graphql_dead
|
28
28
|
@graphql_dead = true
|
29
29
|
end
|
30
30
|
@graphql_result_name = result_name
|
31
|
+
@graphql_is_non_null_in_parent = is_non_null_in_parent
|
31
32
|
# Jump through some hoops to avoid creating this duplicate storage if at all possible.
|
32
33
|
@graphql_metadata = nil
|
33
34
|
end
|
@@ -42,22 +43,14 @@ module GraphQL
|
|
42
43
|
end
|
43
44
|
|
44
45
|
attr_accessor :graphql_dead
|
45
|
-
attr_reader :graphql_parent, :graphql_result_name
|
46
|
-
|
47
|
-
# Although these are used by only one of the Result classes,
|
48
|
-
# it's handy to have the methods implemented on both (even though they just return `nil`)
|
49
|
-
# because it makes it easy to check if anything is assigned.
|
50
|
-
# @return [nil, Array<String>]
|
51
|
-
attr_accessor :graphql_non_null_field_names
|
52
|
-
# @return [nil, true]
|
53
|
-
attr_accessor :graphql_non_null_list_items
|
46
|
+
attr_reader :graphql_parent, :graphql_result_name, :graphql_is_non_null_in_parent
|
54
47
|
|
55
48
|
# @return [Hash] Plain-Ruby result data (`@graphql_metadata` contains Result wrapper objects)
|
56
49
|
attr_accessor :graphql_result_data
|
57
50
|
end
|
58
51
|
|
59
52
|
class GraphQLResultHash
|
60
|
-
def initialize(_result_name, _parent_result)
|
53
|
+
def initialize(_result_name, _parent_result, _is_non_null_in_parent)
|
61
54
|
super
|
62
55
|
@graphql_result_data = {}
|
63
56
|
end
|
@@ -131,7 +124,7 @@ module GraphQL
|
|
131
124
|
when GraphQLResultArray
|
132
125
|
# There's no special handling of arrays because currently, there's no way to split the execution
|
133
126
|
# of a list over several concurrent flows.
|
134
|
-
|
127
|
+
into_result.set_child_result(key, value)
|
135
128
|
else
|
136
129
|
# We have to assume that, since this passed the `fields_will_merge` selection,
|
137
130
|
# that the old and new values are the same.
|
@@ -145,7 +138,7 @@ module GraphQL
|
|
145
138
|
class GraphQLResultArray
|
146
139
|
include GraphQLResult
|
147
140
|
|
148
|
-
def initialize(_result_name, _parent_result)
|
141
|
+
def initialize(_result_name, _parent_result, _is_non_null_in_parent)
|
149
142
|
super
|
150
143
|
@graphql_result_data = []
|
151
144
|
end
|
@@ -189,10 +182,6 @@ module GraphQL
|
|
189
182
|
end
|
190
183
|
end
|
191
184
|
|
192
|
-
class GraphQLSelectionSet < Hash
|
193
|
-
attr_accessor :graphql_directives
|
194
|
-
end
|
195
|
-
|
196
185
|
# @return [GraphQL::Query]
|
197
186
|
attr_reader :query
|
198
187
|
|
@@ -204,14 +193,12 @@ module GraphQL
|
|
204
193
|
|
205
194
|
def initialize(query:, lazies_at_depth:)
|
206
195
|
@query = query
|
196
|
+
@current_trace = query.current_trace
|
207
197
|
@dataloader = query.multiplex.dataloader
|
208
198
|
@lazies_at_depth = lazies_at_depth
|
209
199
|
@schema = query.schema
|
210
200
|
@context = query.context
|
211
|
-
@
|
212
|
-
# Start this off empty:
|
213
|
-
Thread.current[:__graphql_runtime_info] = nil
|
214
|
-
@response = GraphQLResultHash.new(nil, nil)
|
201
|
+
@response = GraphQLResultHash.new(nil, nil, false)
|
215
202
|
# Identify runtime directives by checking which of this schema's directives have overridden `def self.resolve`
|
216
203
|
@runtime_directive_names = []
|
217
204
|
noop_resolve_owner = GraphQL::Schema::Directive.singleton_class
|
@@ -225,8 +212,11 @@ module GraphQL
|
|
225
212
|
# Which assumes that MyObject.get_field("myField") will return the same field
|
226
213
|
# during the lifetime of a query
|
227
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
|
228
217
|
# { Class => Boolean }
|
229
218
|
@lazy_cache = {}
|
219
|
+
@lazy_cache.compare_by_identity
|
230
220
|
end
|
231
221
|
|
232
222
|
def final_result
|
@@ -257,15 +247,15 @@ module GraphQL
|
|
257
247
|
st = get_current_runtime_state
|
258
248
|
st.current_object = query.root_value
|
259
249
|
st.current_result = @response
|
260
|
-
|
261
|
-
|
250
|
+
runtime_object = root_type.wrap(query.root_value, context)
|
251
|
+
runtime_object = schema.sync_lazy(runtime_object)
|
262
252
|
|
263
|
-
if
|
253
|
+
if runtime_object.nil?
|
264
254
|
# Root .authorized? returned false.
|
265
255
|
@response = nil
|
266
256
|
else
|
267
|
-
call_method_on_directives(:resolve,
|
268
|
-
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)
|
269
259
|
# This is kind of a hack -- `gathered_selections` is an Array if any of the selections
|
270
260
|
# require isolation during execution (because of runtime directives). In that case,
|
271
261
|
# make a new, isolated result hash for writing the result into. (That isolated response
|
@@ -275,7 +265,7 @@ module GraphQL
|
|
275
265
|
# directly evaluated and the results can be written right into the main response hash.
|
276
266
|
tap_or_each(gathered_selections) do |selections, is_selection_array|
|
277
267
|
if is_selection_array
|
278
|
-
selection_response = GraphQLResultHash.new(nil, nil)
|
268
|
+
selection_response = GraphQLResultHash.new(nil, nil, false)
|
279
269
|
final_response = @response
|
280
270
|
else
|
281
271
|
selection_response = @response
|
@@ -286,10 +276,13 @@ module GraphQL
|
|
286
276
|
st = get_current_runtime_state
|
287
277
|
st.current_object = query.root_value
|
288
278
|
st.current_result = selection_response
|
289
|
-
|
290
|
-
|
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
|
291
284
|
evaluate_selections(
|
292
|
-
|
285
|
+
runtime_object,
|
293
286
|
root_type,
|
294
287
|
root_op_type == "mutation",
|
295
288
|
selections,
|
@@ -306,7 +299,7 @@ module GraphQL
|
|
306
299
|
nil
|
307
300
|
end
|
308
301
|
|
309
|
-
def gather_selections(owner_object, owner_type, selections, selections_to_run = nil, selections_by_name =
|
302
|
+
def gather_selections(owner_object, owner_type, selections, selections_to_run = nil, selections_by_name = {})
|
310
303
|
selections.each do |node|
|
311
304
|
# Skip gathering this if the directive says so
|
312
305
|
if !directives_include?(node, owner_object, owner_type)
|
@@ -332,8 +325,8 @@ module GraphQL
|
|
332
325
|
else
|
333
326
|
# This is an InlineFragment or a FragmentSpread
|
334
327
|
if @runtime_directive_names.any? && node.directives.any? { |d| @runtime_directive_names.include?(d.name) }
|
335
|
-
next_selections =
|
336
|
-
next_selections
|
328
|
+
next_selections = {}
|
329
|
+
next_selections[:graphql_directives] = node.directives
|
337
330
|
if selections_to_run
|
338
331
|
selections_to_run << next_selections
|
339
332
|
else
|
@@ -350,25 +343,26 @@ module GraphQL
|
|
350
343
|
if node.type
|
351
344
|
type_defn = schema.get_type(node.type.name, context)
|
352
345
|
|
353
|
-
|
354
|
-
|
355
|
-
if
|
356
|
-
|
357
|
-
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
|
358
350
|
end
|
359
351
|
end
|
360
352
|
else
|
361
353
|
# it's an untyped fragment, definitely continue
|
362
|
-
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
|
363
358
|
end
|
364
359
|
when GraphQL::Language::Nodes::FragmentSpread
|
365
360
|
fragment_def = query.fragments[node.name]
|
366
361
|
type_defn = query.get_type(fragment_def.type.name)
|
367
|
-
|
368
|
-
|
369
|
-
if
|
370
|
-
|
371
|
-
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
|
372
366
|
end
|
373
367
|
end
|
374
368
|
else
|
@@ -400,6 +394,12 @@ module GraphQL
|
|
400
394
|
selections_result.merge_into(target_result)
|
401
395
|
end
|
402
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
|
403
403
|
end
|
404
404
|
|
405
405
|
selections_result
|
@@ -441,19 +441,14 @@ module GraphQL
|
|
441
441
|
# the field's return type at this path in order
|
442
442
|
# to propagate `null`
|
443
443
|
return_type_non_null = return_type.non_null?
|
444
|
-
if return_type_non_null
|
445
|
-
(selections_result.graphql_non_null_field_names ||= []).push(result_name)
|
446
|
-
end
|
447
444
|
# Set this before calling `run_with_directives`, so that the directive can have the latest path
|
448
445
|
st = get_current_runtime_state
|
449
446
|
st.current_field = field_defn
|
450
447
|
st.current_result = selections_result
|
451
448
|
st.current_result_name = result_name
|
452
449
|
|
453
|
-
object = owner_object
|
454
|
-
|
455
450
|
if is_introspection
|
456
|
-
|
451
|
+
owner_object = field_defn.owner.wrap(owner_object, context)
|
457
452
|
end
|
458
453
|
|
459
454
|
total_args_count = field_defn.arguments(context).size
|
@@ -461,20 +456,20 @@ module GraphQL
|
|
461
456
|
resolved_arguments = GraphQL::Execution::Interpreter::Arguments::EMPTY
|
462
457
|
if field_defn.extras.size == 0
|
463
458
|
evaluate_selection_with_resolved_keyword_args(
|
464
|
-
NO_ARGS, resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type,
|
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
|
465
460
|
)
|
466
461
|
else
|
467
|
-
evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type,
|
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)
|
468
463
|
end
|
469
464
|
else
|
470
|
-
@query.arguments_cache.dataload_for(ast_node, field_defn,
|
471
|
-
evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_type,
|
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)
|
472
467
|
end
|
473
468
|
end
|
474
469
|
end
|
475
470
|
|
476
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
|
477
|
-
after_lazy(arguments,
|
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|
|
478
473
|
if resolved_arguments.is_a?(GraphQL::ExecutionError) || resolved_arguments.is_a?(GraphQL::UnauthorizedError)
|
479
474
|
continue_value(resolved_arguments, owner_type, field_defn, return_type_non_null, ast_node, result_name, selection_result)
|
480
475
|
next
|
@@ -552,7 +547,7 @@ module GraphQL
|
|
552
547
|
field_result = call_method_on_directives(:resolve, object, directives) do
|
553
548
|
# Actually call the field resolver and capture the result
|
554
549
|
app_result = begin
|
555
|
-
|
550
|
+
@current_trace.execute_field(field: field_defn, ast_node: ast_node, query: query, object: object, arguments: kwarg_arguments) do
|
556
551
|
field_defn.resolve(object, kwarg_arguments, context)
|
557
552
|
end
|
558
553
|
rescue GraphQL::ExecutionError => err
|
@@ -564,7 +559,7 @@ module GraphQL
|
|
564
559
|
ex_err
|
565
560
|
end
|
566
561
|
end
|
567
|
-
after_lazy(app_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|
|
568
563
|
continue_value = continue_value(inner_result, owner_type, field_defn, return_type_non_null, ast_node, result_name, selection_result)
|
569
564
|
if HALT != continue_value
|
570
565
|
continue_field(continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, resolved_arguments, result_name, selection_result)
|
@@ -588,13 +583,9 @@ module GraphQL
|
|
588
583
|
selection_result.graphql_dead # || ((parent = selection_result.graphql_parent) && parent.graphql_dead)
|
589
584
|
end
|
590
585
|
|
591
|
-
def set_result(selection_result, result_name, value, is_child_result)
|
586
|
+
def set_result(selection_result, result_name, value, is_child_result, is_non_null)
|
592
587
|
if !dead_result?(selection_result)
|
593
|
-
if value.nil? &&
|
594
|
-
( # there are two conditions under which `nil` is not allowed in the response:
|
595
|
-
(selection_result.graphql_non_null_list_items) || # this value would be written into a list that doesn't allow nils
|
596
|
-
((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
|
597
|
-
)
|
588
|
+
if value.nil? && is_non_null
|
598
589
|
# This is an invalid nil that should be propagated
|
599
590
|
# One caller of this method passes a block,
|
600
591
|
# namely when application code returns a `nil` to GraphQL and it doesn't belong there.
|
@@ -604,11 +595,12 @@ module GraphQL
|
|
604
595
|
# TODO the code is trying to tell me something.
|
605
596
|
yield if block_given?
|
606
597
|
parent = selection_result.graphql_parent
|
607
|
-
name_in_parent = selection_result.graphql_result_name
|
608
598
|
if parent.nil? # This is a top-level result hash
|
609
599
|
@response = nil
|
610
600
|
else
|
611
|
-
|
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)
|
612
604
|
set_graphql_dead(selection_result)
|
613
605
|
end
|
614
606
|
elsif is_child_result
|
@@ -650,13 +642,13 @@ module GraphQL
|
|
650
642
|
case value
|
651
643
|
when nil
|
652
644
|
if is_non_null
|
653
|
-
set_result(selection_result, result_name, nil, false) do
|
645
|
+
set_result(selection_result, result_name, nil, false, is_non_null) do
|
654
646
|
# This block is called if `result_name` is not dead. (Maybe a previous invalid nil caused it be marked dead.)
|
655
647
|
err = parent_type::InvalidNullError.new(parent_type, field, value)
|
656
648
|
schema.type_error(err, context)
|
657
649
|
end
|
658
650
|
else
|
659
|
-
set_result(selection_result, result_name, nil, false)
|
651
|
+
set_result(selection_result, result_name, nil, false, is_non_null)
|
660
652
|
end
|
661
653
|
HALT
|
662
654
|
when GraphQL::Error
|
@@ -669,7 +661,7 @@ module GraphQL
|
|
669
661
|
value.ast_node ||= ast_node
|
670
662
|
context.errors << value
|
671
663
|
if selection_result
|
672
|
-
set_result(selection_result, result_name, nil, false)
|
664
|
+
set_result(selection_result, result_name, nil, false, is_non_null)
|
673
665
|
end
|
674
666
|
end
|
675
667
|
HALT
|
@@ -723,9 +715,9 @@ module GraphQL
|
|
723
715
|
if selection_result
|
724
716
|
if list_type_at_all
|
725
717
|
result_without_errors = value.map { |v| v.is_a?(GraphQL::ExecutionError) ? nil : v }
|
726
|
-
set_result(selection_result, result_name, result_without_errors, false)
|
718
|
+
set_result(selection_result, result_name, result_without_errors, false, is_non_null)
|
727
719
|
else
|
728
|
-
set_result(selection_result, result_name, nil, false)
|
720
|
+
set_result(selection_result, result_name, nil, false, is_non_null)
|
729
721
|
end
|
730
722
|
end
|
731
723
|
end
|
@@ -735,7 +727,7 @@ module GraphQL
|
|
735
727
|
end
|
736
728
|
when GraphQL::Execution::Interpreter::RawValue
|
737
729
|
# Write raw value directly to the response without resolving nested objects
|
738
|
-
set_result(selection_result, result_name, value.resolve, false)
|
730
|
+
set_result(selection_result, result_name, value.resolve, false, is_non_null)
|
739
731
|
HALT
|
740
732
|
else
|
741
733
|
value
|
@@ -763,11 +755,11 @@ module GraphQL
|
|
763
755
|
rescue StandardError => err
|
764
756
|
schema.handle_or_reraise(context, err)
|
765
757
|
end
|
766
|
-
set_result(selection_result, result_name, r, false)
|
758
|
+
set_result(selection_result, result_name, r, false, is_non_null)
|
767
759
|
r
|
768
760
|
when "UNION", "INTERFACE"
|
769
761
|
resolved_type_or_lazy = resolve_type(current_type, value)
|
770
|
-
after_lazy(resolved_type_or_lazy,
|
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|
|
771
763
|
if resolved_type_result.is_a?(Array) && resolved_type_result.length == 2
|
772
764
|
resolved_type, resolved_value = resolved_type_result
|
773
765
|
else
|
@@ -781,7 +773,7 @@ module GraphQL
|
|
781
773
|
err_class = current_type::UnresolvedTypeError
|
782
774
|
type_error = err_class.new(resolved_value, field, parent_type, resolved_type, possible_types)
|
783
775
|
schema.type_error(type_error, context)
|
784
|
-
set_result(selection_result, result_name, nil, false)
|
776
|
+
set_result(selection_result, result_name, nil, false, is_non_null)
|
785
777
|
nil
|
786
778
|
else
|
787
779
|
continue_field(resolved_value, owner_type, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result)
|
@@ -789,15 +781,15 @@ module GraphQL
|
|
789
781
|
end
|
790
782
|
when "OBJECT"
|
791
783
|
object_proxy = begin
|
792
|
-
|
784
|
+
current_type.wrap(value, context)
|
793
785
|
rescue GraphQL::ExecutionError => err
|
794
786
|
err
|
795
787
|
end
|
796
|
-
after_lazy(object_proxy,
|
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|
|
797
789
|
continue_value = continue_value(inner_object, owner_type, field, is_non_null, ast_node, result_name, selection_result)
|
798
790
|
if HALT != continue_value
|
799
|
-
response_hash = GraphQLResultHash.new(result_name, selection_result)
|
800
|
-
set_result(selection_result, result_name, response_hash, true)
|
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)
|
801
793
|
gathered_selections = gather_selections(continue_value, current_type, next_selections)
|
802
794
|
# There are two possibilities for `gathered_selections`:
|
803
795
|
# 1. All selections of this object should be evaluated together (there are no runtime directives modifying execution).
|
@@ -809,7 +801,7 @@ module GraphQL
|
|
809
801
|
# (Technically, it's possible that one of those entries _doesn't_ require isolation.)
|
810
802
|
tap_or_each(gathered_selections) do |selections, is_selection_array|
|
811
803
|
if is_selection_array
|
812
|
-
this_result = GraphQLResultHash.new(result_name, selection_result)
|
804
|
+
this_result = GraphQLResultHash.new(result_name, selection_result, is_non_null)
|
813
805
|
final_result = response_hash
|
814
806
|
else
|
815
807
|
this_result = response_hash
|
@@ -822,7 +814,11 @@ module GraphQL
|
|
822
814
|
st.current_result_name = nil
|
823
815
|
st.current_result = this_result
|
824
816
|
|
825
|
-
|
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
|
826
822
|
evaluate_selections(
|
827
823
|
continue_value,
|
828
824
|
current_type,
|
@@ -842,12 +838,12 @@ module GraphQL
|
|
842
838
|
# This is true for objects, unions, and interfaces
|
843
839
|
use_dataloader_job = !inner_type.unwrap.kind.input?
|
844
840
|
inner_type_non_null = inner_type.non_null?
|
845
|
-
response_list = GraphQLResultArray.new(result_name, selection_result)
|
846
|
-
response_list
|
847
|
-
|
848
|
-
idx = 0
|
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
|
849
844
|
list_value = begin
|
850
845
|
value.each do |inner_value|
|
846
|
+
idx ||= 0
|
851
847
|
this_idx = idx
|
852
848
|
idx += 1
|
853
849
|
if use_dataloader_job
|
@@ -878,8 +874,9 @@ module GraphQL
|
|
878
874
|
ex_err
|
879
875
|
end
|
880
876
|
end
|
881
|
-
|
882
|
-
|
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)
|
883
880
|
else
|
884
881
|
raise "Invariant: Unhandled type kind #{current_type.kind} (#{current_type})"
|
885
882
|
end
|
@@ -891,7 +888,7 @@ module GraphQL
|
|
891
888
|
st.current_result = response_list
|
892
889
|
call_method_on_directives(:resolve_each, owner_object, ast_node.directives) do
|
893
890
|
# This will update `response_list` with the lazy
|
894
|
-
after_lazy(inner_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|
|
895
892
|
continue_value = continue_value(inner_inner_value, owner_type, field, inner_type_non_null, ast_node, this_idx, response_list)
|
896
893
|
if HALT != continue_value
|
897
894
|
continue_field(continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments, this_idx, response_list)
|
@@ -945,7 +942,25 @@ module GraphQL
|
|
945
942
|
end
|
946
943
|
|
947
944
|
def get_current_runtime_state
|
948
|
-
Thread.current[:__graphql_runtime_info] ||=
|
945
|
+
current_state = Thread.current[:__graphql_runtime_info] ||= begin
|
946
|
+
per_query_state = {}
|
947
|
+
per_query_state.compare_by_identity
|
948
|
+
per_query_state
|
949
|
+
end
|
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)
|
963
|
+
end
|
949
964
|
end
|
950
965
|
|
951
966
|
# @param obj [Object] Some user-returned value that may want to be batched
|
@@ -953,7 +968,7 @@ module GraphQL
|
|
953
968
|
# @param eager [Boolean] Set to `true` for mutation root fields only
|
954
969
|
# @param trace [Boolean] If `false`, don't wrap this with field tracing
|
955
970
|
# @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.
|
956
|
-
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)
|
957
972
|
if lazy?(lazy_obj)
|
958
973
|
orig_result = result
|
959
974
|
lazy = GraphQL::Execution::Lazy.new(field: field) do
|
@@ -967,7 +982,7 @@ module GraphQL
|
|
967
982
|
# but don't wrap the continuation below
|
968
983
|
inner_obj = begin
|
969
984
|
if trace
|
970
|
-
|
985
|
+
@current_trace.execute_field_lazy(field: field, query: query, object: owner_object, arguments: arguments, ast_node: ast_node) do
|
971
986
|
schema.sync_lazy(lazy_obj)
|
972
987
|
end
|
973
988
|
else
|
@@ -988,7 +1003,7 @@ module GraphQL
|
|
988
1003
|
if eager
|
989
1004
|
lazy.value
|
990
1005
|
else
|
991
|
-
set_result(result, result_name, lazy, false)
|
1006
|
+
set_result(result, result_name, lazy, false, false) # is_non_null is irrelevant here
|
992
1007
|
current_depth = 0
|
993
1008
|
while result
|
994
1009
|
current_depth += 1
|
@@ -1013,17 +1028,24 @@ module GraphQL
|
|
1013
1028
|
end
|
1014
1029
|
|
1015
1030
|
def delete_all_interpreter_context
|
1016
|
-
Thread.current[:__graphql_runtime_info]
|
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
|
1017
1039
|
end
|
1018
1040
|
|
1019
1041
|
def resolve_type(type, value)
|
1020
|
-
resolved_type, resolved_value =
|
1042
|
+
resolved_type, resolved_value = @current_trace.resolve_type(query: query, type: type, object: value) do
|
1021
1043
|
query.resolve_type(type, value)
|
1022
1044
|
end
|
1023
1045
|
|
1024
1046
|
if lazy?(resolved_type)
|
1025
1047
|
GraphQL::Execution::Lazy.new do
|
1026
|
-
|
1048
|
+
@current_trace.resolve_type_lazy(query: query, type: type, object: value) do
|
1027
1049
|
schema.sync_lazy(resolved_type)
|
1028
1050
|
end
|
1029
1051
|
end
|
@@ -1032,14 +1054,13 @@ module GraphQL
|
|
1032
1054
|
end
|
1033
1055
|
end
|
1034
1056
|
|
1035
|
-
def authorized_new(type, value, context)
|
1036
|
-
type.authorized_new(value, context)
|
1037
|
-
end
|
1038
|
-
|
1039
1057
|
def lazy?(object)
|
1040
|
-
|
1041
|
-
|
1042
|
-
|
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
|
1043
1064
|
end
|
1044
1065
|
end
|
1045
1066
|
end
|
@@ -87,7 +87,6 @@ module GraphQL
|
|
87
87
|
|
88
88
|
# Then, work through lazy results in a breadth-first way
|
89
89
|
multiplex.dataloader.append_job {
|
90
|
-
tracer = multiplex
|
91
90
|
query = multiplex.queries.length == 1 ? multiplex.queries[0] : nil
|
92
91
|
queries = multiplex ? multiplex.queries : [query]
|
93
92
|
final_values = queries.map do |query|
|
@@ -96,7 +95,7 @@ module GraphQL
|
|
96
95
|
runtime ? runtime.final_result : nil
|
97
96
|
end
|
98
97
|
final_values.compact!
|
99
|
-
|
98
|
+
multiplex.current_trace.execute_query_lazy(multiplex: multiplex, query: query) do
|
100
99
|
Interpreter::Resolve.resolve_each_depth(lazies_at_depth, multiplex.dataloader)
|
101
100
|
end
|
102
101
|
queries.each do |query|
|
@@ -55,7 +55,7 @@ module GraphQL
|
|
55
55
|
@arguments
|
56
56
|
else
|
57
57
|
@arguments = if @field
|
58
|
-
@query.
|
58
|
+
@query.after_lazy(@query.arguments_for(@ast_nodes.first, @field)) do |args|
|
59
59
|
args.is_a?(Execution::Interpreter::Arguments) ? args.keyword_arguments : args
|
60
60
|
end
|
61
61
|
else
|
data/lib/graphql/filter.rb
CHANGED
@@ -6,7 +6,8 @@ module GraphQL
|
|
6
6
|
class Filter
|
7
7
|
def initialize(only: nil, except: nil, silence_deprecation_warning: false)
|
8
8
|
if !silence_deprecation_warning
|
9
|
-
|
9
|
+
line = caller(2, 10).find { |l| !l.include?("lib/graphql") }
|
10
|
+
GraphQL::Deprecation.warn("GraphQL::Filter, `only:`, `except:`, and `.merge_filters` are deprecated and will be removed in v2.1.0. Implement `visible?` on your schema members instead (https://graphql-ruby.org/authorization/visibility.html).\n #{line}")
|
10
11
|
end
|
11
12
|
@only = only
|
12
13
|
@except = except
|
@@ -11,7 +11,7 @@ module GraphQL
|
|
11
11
|
# Apply wrapping manually since this field isn't wrapped by instrumentation
|
12
12
|
schema = @context.query.schema
|
13
13
|
schema_type = schema.introspection_system.types["__Schema"]
|
14
|
-
schema_type.
|
14
|
+
schema_type.wrap(schema, @context)
|
15
15
|
end
|
16
16
|
|
17
17
|
def __type(name:)
|
@@ -24,17 +24,24 @@ module GraphQL
|
|
24
24
|
@include_built_in_directives = include_built_in_directives
|
25
25
|
@include_one_of = false
|
26
26
|
|
27
|
-
|
28
|
-
|
29
|
-
|
27
|
+
schema_context = schema.context_class.new(query: nil, object: nil, schema: schema, values: context)
|
28
|
+
|
29
|
+
@warden = if only || except
|
30
|
+
filter = GraphQL::Filter
|
31
|
+
.new(only: only, except: except)
|
32
|
+
.merge(only: @schema.method(:visible?))
|
33
|
+
GraphQL::Schema::Warden.new(
|
34
|
+
filter,
|
35
|
+
schema: @schema,
|
36
|
+
context: schema_context,
|
37
|
+
)
|
38
|
+
else
|
39
|
+
@schema.warden_class.new(
|
40
|
+
schema: @schema,
|
41
|
+
context: schema_context,
|
42
|
+
)
|
30
43
|
end
|
31
44
|
|
32
|
-
schema_context = schema.context_class.new(query: nil, object: nil, schema: schema, values: context)
|
33
|
-
@warden = GraphQL::Schema::Warden.new(
|
34
|
-
filter,
|
35
|
-
schema: @schema,
|
36
|
-
context: schema_context,
|
37
|
-
)
|
38
45
|
schema_context.warden = @warden
|
39
46
|
end
|
40
47
|
|