graphql 2.5.11 → 2.6.10
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/detailed_trace_generator.rb +77 -0
- data/lib/generators/graphql/field_extractor.rb +17 -1
- data/lib/generators/graphql/templates/create_graphql_detailed_traces.erb +10 -0
- data/lib/generators/graphql/templates/schema.erb +2 -1
- data/lib/graphql/analysis/query_complexity.rb +31 -15
- data/lib/graphql/analysis.rb +20 -13
- data/lib/graphql/backtrace/table.rb +10 -1
- data/lib/graphql/current.rb +7 -1
- data/lib/graphql/dashboard/application_controller.rb +45 -0
- data/lib/graphql/dashboard/landings_controller.rb +9 -0
- data/lib/graphql/dashboard/statics_controller.rb +31 -0
- data/lib/graphql/dashboard/subscriptions.rb +2 -1
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +1 -0
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +2 -2
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +1 -1
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +1 -1
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +1 -1
- data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +1 -1
- data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +1 -1
- data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +7 -7
- data/lib/graphql/dashboard.rb +14 -73
- data/lib/graphql/dataloader/async_dataloader.rb +336 -60
- data/lib/graphql/dataloader/null_dataloader.rb +48 -10
- data/lib/graphql/dataloader/source.rb +32 -19
- data/lib/graphql/dataloader.rb +107 -39
- data/lib/graphql/date_encoding_error.rb +1 -1
- data/lib/graphql/execution/directive_checks.rb +2 -0
- data/lib/graphql/execution/field_resolve_step.rb +804 -0
- data/lib/graphql/execution/finalize.rb +229 -0
- data/lib/graphql/execution/input_values.rb +333 -0
- data/lib/graphql/execution/interpreter/arguments_cache.rb +3 -0
- data/lib/graphql/execution/interpreter/handles_raw_value.rb +6 -0
- data/lib/graphql/execution/interpreter/resolve.rb +7 -13
- data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +13 -0
- data/lib/graphql/execution/interpreter/runtime.rb +60 -40
- data/lib/graphql/execution/interpreter.rb +17 -23
- data/lib/graphql/execution/lazy.rb +1 -1
- data/lib/graphql/execution/load_argument_step.rb +102 -0
- data/lib/graphql/execution/multiplex.rb +1 -1
- data/lib/graphql/execution/next.rb +98 -0
- data/lib/graphql/execution/prepare_object_step.rb +151 -0
- data/lib/graphql/execution/resolve_type_step.rb +27 -0
- data/lib/graphql/execution/runner.rb +457 -0
- data/lib/graphql/execution/selections_step.rb +95 -0
- data/lib/graphql/execution.rb +15 -4
- data/lib/graphql/execution_error.rb +17 -12
- data/lib/graphql/float_decoding_error.rb +13 -0
- data/lib/graphql/float_encoding_error.rb +28 -0
- data/lib/graphql/introspection/directive_type.rb +7 -3
- data/lib/graphql/introspection/dynamic_fields.rb +5 -1
- data/lib/graphql/introspection/entry_points.rb +11 -3
- data/lib/graphql/introspection/enum_value_type.rb +5 -5
- data/lib/graphql/introspection/field_type.rb +13 -5
- data/lib/graphql/introspection/input_value_type.rb +21 -13
- data/lib/graphql/introspection/schema_type.rb +6 -2
- data/lib/graphql/introspection/type_type.rb +64 -28
- data/lib/graphql/invalid_null_error.rb +11 -5
- data/lib/graphql/language/block_string.rb +6 -11
- data/lib/graphql/language/cache.rb +84 -14
- data/lib/graphql/language/document_from_schema_definition.rb +2 -1
- data/lib/graphql/language/lexer.rb +27 -17
- data/lib/graphql/language/nodes.rb +2 -1
- data/lib/graphql/language/parser.rb +1 -1
- data/lib/graphql/language.rb +32 -17
- data/lib/graphql/pagination/connection.rb +2 -0
- data/lib/graphql/pagination/connections.rb +30 -0
- data/lib/graphql/pagination/relation_connection.rb +10 -1
- data/lib/graphql/query/context.rb +10 -3
- data/lib/graphql/query/null_context.rb +9 -3
- data/lib/graphql/query/partial.rb +18 -3
- data/lib/graphql/query/variable_validation_error.rb +16 -1
- data/lib/graphql/query/variables.rb +1 -1
- data/lib/graphql/query.rb +12 -3
- data/lib/graphql/railtie.rb +2 -1
- data/lib/graphql/runtime_error.rb +6 -0
- data/lib/graphql/schema/argument.rb +13 -1
- data/lib/graphql/schema/build_from_definition.rb +28 -1
- data/lib/graphql/schema/directive/feature.rb +4 -0
- data/lib/graphql/schema/directive/transform.rb +20 -0
- data/lib/graphql/schema/directive.rb +48 -13
- data/lib/graphql/schema/field/connection_extension.rb +4 -37
- data/lib/graphql/schema/field/scope_extension.rb +18 -13
- data/lib/graphql/schema/field.rb +79 -48
- data/lib/graphql/schema/field_extension.rb +11 -8
- data/lib/graphql/schema/has_single_input_argument.rb +24 -13
- data/lib/graphql/schema/input_object.rb +6 -2
- data/lib/graphql/schema/interface.rb +26 -0
- data/lib/graphql/schema/introspection_system.rb +6 -21
- data/lib/graphql/schema/list.rb +5 -1
- data/lib/graphql/schema/member/base_dsl_methods.rb +1 -11
- data/lib/graphql/schema/member/has_arguments.rb +43 -14
- data/lib/graphql/schema/member/has_authorization.rb +35 -0
- data/lib/graphql/schema/member/has_dataloader.rb +37 -0
- data/lib/graphql/schema/member/has_fields.rb +86 -5
- data/lib/graphql/schema/member.rb +5 -0
- data/lib/graphql/schema/non_null.rb +1 -1
- data/lib/graphql/schema/object.rb +1 -0
- data/lib/graphql/schema/printer.rb +1 -1
- data/lib/graphql/schema/ractor_shareable.rb +1 -0
- data/lib/graphql/schema/relay_classic_mutation.rb +16 -2
- data/lib/graphql/schema/resolver.rb +76 -1
- data/lib/graphql/schema/subscription.rb +53 -10
- data/lib/graphql/schema/timeout.rb +2 -2
- data/lib/graphql/schema/validator/allow_blank_validator.rb +3 -3
- data/lib/graphql/schema/validator/allow_null_validator.rb +3 -3
- data/lib/graphql/schema/validator/exclusion_validator.rb +2 -2
- data/lib/graphql/schema/validator/format_validator.rb +3 -3
- data/lib/graphql/schema/validator/inclusion_validator.rb +2 -2
- data/lib/graphql/schema/validator/length_validator.rb +6 -6
- data/lib/graphql/schema/validator/numericality_validator.rb +19 -19
- data/lib/graphql/schema/validator/required_validator.rb +38 -5
- data/lib/graphql/schema/validator.rb +9 -0
- data/lib/graphql/schema/visibility/profile.rb +74 -53
- data/lib/graphql/schema/visibility/visit.rb +1 -1
- data/lib/graphql/schema/visibility.rb +32 -24
- data/lib/graphql/schema/wrapper.rb +11 -1
- data/lib/graphql/schema.rb +93 -27
- data/lib/graphql/static_validation/base_visitor.rb +90 -66
- data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
- data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +18 -6
- data/lib/graphql/static_validation/rules/arguments_are_defined.rb +5 -2
- data/lib/graphql/static_validation/rules/directives_are_defined.rb +5 -2
- data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +4 -3
- data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +14 -4
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +351 -248
- data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +4 -4
- data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +3 -3
- data/lib/graphql/static_validation/rules/fragment_types_exist.rb +10 -7
- data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +27 -7
- data/lib/graphql/static_validation/rules/variables_are_input_types.rb +12 -9
- data/lib/graphql/static_validation/validation_context.rb +1 -1
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +1 -0
- data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +40 -10
- data/lib/graphql/subscriptions/serialize.rb +10 -4
- data/lib/graphql/subscriptions.rb +36 -1
- data/lib/graphql/testing/helpers.rb +12 -9
- data/lib/graphql/testing/mock_action_cable.rb +111 -0
- data/lib/graphql/testing.rb +1 -0
- data/lib/graphql/tracing/detailed_trace/active_record_backend.rb +74 -0
- data/lib/graphql/tracing/detailed_trace.rb +70 -7
- data/lib/graphql/tracing/perfetto_trace.rb +213 -81
- data/lib/graphql/tracing/sentry_trace.rb +3 -1
- data/lib/graphql/tracing/trace.rb +6 -0
- data/lib/graphql/types/float.rb +18 -4
- data/lib/graphql/types/relay/connection_behaviors.rb +8 -6
- data/lib/graphql/types/relay/edge_behaviors.rb +4 -3
- data/lib/graphql/types/relay/has_node_field.rb +13 -8
- data/lib/graphql/types/relay/has_nodes_field.rb +13 -8
- data/lib/graphql/types/relay/node_behaviors.rb +13 -2
- data/lib/graphql/unauthorized_error.rb +9 -1
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +8 -2
- metadata +23 -59
|
@@ -35,11 +35,10 @@ module GraphQL
|
|
|
35
35
|
# @return [GraphQL::Query::Context]
|
|
36
36
|
attr_reader :context
|
|
37
37
|
|
|
38
|
-
def initialize(query
|
|
38
|
+
def initialize(query:)
|
|
39
39
|
@query = query
|
|
40
40
|
@current_trace = query.current_trace
|
|
41
41
|
@dataloader = query.multiplex.dataloader
|
|
42
|
-
@lazies_at_depth = lazies_at_depth
|
|
43
42
|
@schema = query.schema
|
|
44
43
|
@context = query.context
|
|
45
44
|
@response = nil
|
|
@@ -199,7 +198,7 @@ module GraphQL
|
|
|
199
198
|
|
|
200
199
|
def each_gathered_selections(response_hash)
|
|
201
200
|
ordered_result_keys = []
|
|
202
|
-
gathered_selections = gather_selections(response_hash.graphql_application_value, response_hash.graphql_result_type, response_hash.graphql_selections, nil, {}, ordered_result_keys)
|
|
201
|
+
gathered_selections = gather_selections(response_hash, response_hash.graphql_application_value, response_hash.graphql_result_type, response_hash.graphql_selections, nil, {}, ordered_result_keys)
|
|
203
202
|
ordered_result_keys.uniq!
|
|
204
203
|
if gathered_selections.is_a?(Array)
|
|
205
204
|
gathered_selections.each do |item|
|
|
@@ -210,15 +209,13 @@ module GraphQL
|
|
|
210
209
|
end
|
|
211
210
|
end
|
|
212
211
|
|
|
213
|
-
def gather_selections(owner_object, owner_type, selections, selections_to_run, selections_by_name, ordered_result_keys)
|
|
212
|
+
def gather_selections(graphql_response, owner_object, owner_type, selections, selections_to_run, selections_by_name, ordered_result_keys)
|
|
214
213
|
selections.each do |node|
|
|
215
|
-
# Skip gathering this if the directive says so
|
|
216
|
-
if !directives_include?(node, owner_object, owner_type)
|
|
217
|
-
next
|
|
218
|
-
end
|
|
219
|
-
|
|
220
214
|
if node.is_a?(GraphQL::Language::Nodes::Field)
|
|
221
215
|
response_key = node.alias || node.name
|
|
216
|
+
if !directives_include?(node, owner_object, owner_type, graphql_response, response_key)
|
|
217
|
+
next
|
|
218
|
+
end
|
|
222
219
|
ordered_result_keys << response_key
|
|
223
220
|
selections = selections_by_name[response_key]
|
|
224
221
|
# if there was already a selection of this field,
|
|
@@ -235,6 +232,9 @@ module GraphQL
|
|
|
235
232
|
selections_by_name[response_key] = node
|
|
236
233
|
end
|
|
237
234
|
else
|
|
235
|
+
if !directives_include?(node, owner_object, owner_type, graphql_response, nil)
|
|
236
|
+
next
|
|
237
|
+
end
|
|
238
238
|
# This is an InlineFragment or a FragmentSpread
|
|
239
239
|
if !@runtime_directive_names.empty? && node.directives.any? { |d| @runtime_directive_names.include?(d.name) }
|
|
240
240
|
next_selections = {}
|
|
@@ -256,14 +256,14 @@ module GraphQL
|
|
|
256
256
|
type_defn = query.types.type(node.type.name)
|
|
257
257
|
|
|
258
258
|
if query.types.possible_types(type_defn).include?(owner_type)
|
|
259
|
-
result = gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections, ordered_result_keys)
|
|
259
|
+
result = gather_selections(graphql_response, owner_object, owner_type, node.selections, selections_to_run, next_selections, ordered_result_keys)
|
|
260
260
|
if !result.equal?(next_selections)
|
|
261
261
|
selections_to_run = result
|
|
262
262
|
end
|
|
263
263
|
end
|
|
264
264
|
else
|
|
265
265
|
# it's an untyped fragment, definitely continue
|
|
266
|
-
result = gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections, ordered_result_keys)
|
|
266
|
+
result = gather_selections(graphql_response, owner_object, owner_type, node.selections, selections_to_run, next_selections, ordered_result_keys)
|
|
267
267
|
if !result.equal?(next_selections)
|
|
268
268
|
selections_to_run = result
|
|
269
269
|
end
|
|
@@ -272,7 +272,7 @@ module GraphQL
|
|
|
272
272
|
fragment_def = query.fragments[node.name]
|
|
273
273
|
type_defn = query.types.type(fragment_def.type.name)
|
|
274
274
|
if query.types.possible_types(type_defn).include?(owner_type)
|
|
275
|
-
result = gather_selections(owner_object, owner_type, fragment_def.selections, selections_to_run, next_selections, ordered_result_keys)
|
|
275
|
+
result = gather_selections(graphql_response, owner_object, owner_type, fragment_def.selections, selections_to_run, next_selections, ordered_result_keys)
|
|
276
276
|
if !result.equal?(next_selections)
|
|
277
277
|
selections_to_run = result
|
|
278
278
|
end
|
|
@@ -340,7 +340,7 @@ module GraphQL
|
|
|
340
340
|
end
|
|
341
341
|
field_name = ast_node.name
|
|
342
342
|
owner_type = selections_result.graphql_result_type
|
|
343
|
-
field_defn = query.types.field(owner_type, field_name)
|
|
343
|
+
field_defn = query.types.field(owner_type, field_name) || raise(GraphQL::Error, "No field definition found for #{owner_type.graphql_name}.#{field_name} (at #{ast_node.position})")
|
|
344
344
|
|
|
345
345
|
# Set this before calling `run_with_directives`, so that the directive can have the latest path
|
|
346
346
|
runtime_state = get_current_runtime_state
|
|
@@ -365,6 +365,10 @@ module GraphQL
|
|
|
365
365
|
else
|
|
366
366
|
@query.arguments_cache.dataload_for(ast_node, field_defn, owner_object) do |resolved_arguments|
|
|
367
367
|
runtime_state = get_current_runtime_state # This might be in a different fiber
|
|
368
|
+
runtime_state.current_field = field_defn
|
|
369
|
+
runtime_state.current_arguments = resolved_arguments
|
|
370
|
+
runtime_state.current_result_name = result_name
|
|
371
|
+
runtime_state.current_result = selections_result
|
|
368
372
|
evaluate_selection_with_args(resolved_arguments, field_defn, ast_node, field_ast_nodes, owner_object, result_name, selections_result, runtime_state)
|
|
369
373
|
end
|
|
370
374
|
end
|
|
@@ -373,6 +377,8 @@ module GraphQL
|
|
|
373
377
|
def evaluate_selection_with_args(arguments, field_defn, ast_node, field_ast_nodes, object, result_name, selection_result, runtime_state) # rubocop:disable Metrics/ParameterLists
|
|
374
378
|
after_lazy(arguments, field: field_defn, ast_node: ast_node, owner_object: object, arguments: arguments, result_name: result_name, result: selection_result, runtime_state: runtime_state) do |resolved_arguments, runtime_state|
|
|
375
379
|
if resolved_arguments.is_a?(GraphQL::ExecutionError) || resolved_arguments.is_a?(GraphQL::UnauthorizedError)
|
|
380
|
+
next if selection_result.collect_result(result_name, resolved_arguments)
|
|
381
|
+
|
|
376
382
|
return_type_non_null = field_defn.type.non_null?
|
|
377
383
|
continue_value(resolved_arguments, field_defn, return_type_non_null, ast_node, result_name, selection_result)
|
|
378
384
|
next
|
|
@@ -446,7 +452,7 @@ module GraphQL
|
|
|
446
452
|
}
|
|
447
453
|
end
|
|
448
454
|
|
|
449
|
-
|
|
455
|
+
call_method_on_directives(:resolve, object, directives) do
|
|
450
456
|
if !directives.empty?
|
|
451
457
|
# This might be executed in a different context; reset this info
|
|
452
458
|
runtime_state = get_current_runtime_state
|
|
@@ -472,6 +478,8 @@ module GraphQL
|
|
|
472
478
|
end
|
|
473
479
|
@current_trace.end_execute_field(field_defn, object, kwarg_arguments, query, app_result)
|
|
474
480
|
after_lazy(app_result, field: field_defn, ast_node: ast_node, owner_object: object, arguments: resolved_arguments, result_name: result_name, result: selection_result, runtime_state: runtime_state) do |inner_result, runtime_state|
|
|
481
|
+
next if selection_result.collect_result(result_name, inner_result)
|
|
482
|
+
|
|
475
483
|
owner_type = selection_result.graphql_result_type
|
|
476
484
|
return_type = field_defn.type
|
|
477
485
|
continue_value = continue_value(inner_result, field_defn, return_type.non_null?, ast_node, result_name, selection_result)
|
|
@@ -484,12 +492,6 @@ module GraphQL
|
|
|
484
492
|
end
|
|
485
493
|
end
|
|
486
494
|
end
|
|
487
|
-
# If this field is a root mutation field, immediately resolve
|
|
488
|
-
# all of its child fields before moving on to the next root mutation field.
|
|
489
|
-
# (Subselections of this mutation will still be resolved level-by-level.)
|
|
490
|
-
if selection_result.graphql_is_eager
|
|
491
|
-
Interpreter::Resolve.resolve_all([field_result], @dataloader)
|
|
492
|
-
end
|
|
493
495
|
end
|
|
494
496
|
|
|
495
497
|
def set_result(selection_result, result_name, value, is_child_result, is_non_null)
|
|
@@ -572,7 +574,7 @@ module GraphQL
|
|
|
572
574
|
value.path ||= current_path
|
|
573
575
|
value.ast_node ||= ast_node
|
|
574
576
|
context.errors << value
|
|
575
|
-
if selection_result
|
|
577
|
+
if selection_result && result_name
|
|
576
578
|
set_result(selection_result, result_name, nil, false, is_non_null)
|
|
577
579
|
end
|
|
578
580
|
end
|
|
@@ -596,7 +598,7 @@ module GraphQL
|
|
|
596
598
|
err
|
|
597
599
|
end
|
|
598
600
|
continue_value(next_value, field, is_non_null, ast_node, result_name, selection_result)
|
|
599
|
-
elsif GraphQL::Execution::
|
|
601
|
+
elsif value.is_a?(GraphQL::Execution::Skip)
|
|
600
602
|
# It's possible a lazy was already written here
|
|
601
603
|
case selection_result
|
|
602
604
|
when GraphQLResultHash
|
|
@@ -667,7 +669,11 @@ module GraphQL
|
|
|
667
669
|
rescue GraphQL::ExecutionError => ex_err
|
|
668
670
|
return continue_value(ex_err, field, is_non_null, ast_node, result_name, selection_result)
|
|
669
671
|
rescue StandardError => err
|
|
670
|
-
|
|
672
|
+
begin
|
|
673
|
+
query.handle_or_reraise(err)
|
|
674
|
+
rescue GraphQL::ExecutionError => ex_err
|
|
675
|
+
return continue_value(ex_err, field, is_non_null, ast_node, result_name, selection_result)
|
|
676
|
+
end
|
|
671
677
|
end
|
|
672
678
|
set_result(selection_result, result_name, r, false, is_non_null)
|
|
673
679
|
r
|
|
@@ -750,7 +756,7 @@ module GraphQL
|
|
|
750
756
|
idx += 1
|
|
751
757
|
if use_dataloader_job
|
|
752
758
|
@dataloader.append_job do
|
|
753
|
-
resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, owner_type, was_scoped,
|
|
759
|
+
resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, owner_type, was_scoped, nil)
|
|
754
760
|
end
|
|
755
761
|
else
|
|
756
762
|
resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, owner_type, was_scoped, runtime_state)
|
|
@@ -759,8 +765,7 @@ module GraphQL
|
|
|
759
765
|
|
|
760
766
|
response_list
|
|
761
767
|
rescue NoMethodError => err
|
|
762
|
-
|
|
763
|
-
if err.name == :each && (err.respond_to?(:receiver) ? err.receiver == value : true)
|
|
768
|
+
if err.name == :each && err.receiver == value
|
|
764
769
|
# This happens when the GraphQL schema doesn't match the implementation. Help the dev debug.
|
|
765
770
|
raise ListResultFailedError.new(value: value, field: field, path: current_path)
|
|
766
771
|
else
|
|
@@ -792,6 +797,7 @@ module GraphQL
|
|
|
792
797
|
end
|
|
793
798
|
|
|
794
799
|
def resolve_list_item(inner_value, inner_type, inner_type_non_null, ast_node, field, owner_object, arguments, this_idx, response_list, owner_type, was_scoped, runtime_state) # rubocop:disable Metrics/ParameterLists
|
|
800
|
+
runtime_state ||= get_current_runtime_state
|
|
795
801
|
runtime_state.current_result_name = this_idx
|
|
796
802
|
runtime_state.current_result = response_list
|
|
797
803
|
call_method_on_directives(:resolve_each, owner_object, ast_node.directives) do
|
|
@@ -844,11 +850,31 @@ module GraphQL
|
|
|
844
850
|
end
|
|
845
851
|
|
|
846
852
|
# Check {Schema::Directive.include?} for each directive that's present
|
|
847
|
-
def directives_include?(node, graphql_object, parent_type)
|
|
853
|
+
def directives_include?(node, graphql_object, parent_type, selection_result, extra_path_part)
|
|
848
854
|
node.directives.each do |dir_node|
|
|
849
855
|
dir_defn = @schema_directives.fetch(dir_node.name)
|
|
850
|
-
|
|
851
|
-
if !
|
|
856
|
+
raw_dir_args = arguments(nil, dir_defn, dir_node)
|
|
857
|
+
if !raw_dir_args.is_a?(GraphQL::ExecutionError)
|
|
858
|
+
begin
|
|
859
|
+
dir_defn.validate!(raw_dir_args, context)
|
|
860
|
+
rescue GraphQL::ExecutionError => err
|
|
861
|
+
raw_dir_args = err
|
|
862
|
+
end
|
|
863
|
+
end
|
|
864
|
+
|
|
865
|
+
if extra_path_part && raw_dir_args.is_a?(GraphQL::ExecutionError)
|
|
866
|
+
raw_dir_args.path = current_path + [extra_path_part]
|
|
867
|
+
end
|
|
868
|
+
|
|
869
|
+
dir_args = continue_value(
|
|
870
|
+
raw_dir_args, # value
|
|
871
|
+
nil, # field
|
|
872
|
+
false, # is_non_null
|
|
873
|
+
dir_node, # ast_node
|
|
874
|
+
nil, # result_name
|
|
875
|
+
selection_result
|
|
876
|
+
)
|
|
877
|
+
if dir_args == HALT || !dir_defn.include?(graphql_object, dir_args, context)
|
|
852
878
|
return false
|
|
853
879
|
end
|
|
854
880
|
end
|
|
@@ -879,7 +905,6 @@ module GraphQL
|
|
|
879
905
|
# @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.
|
|
880
906
|
def after_lazy(lazy_obj, field:, owner_object:, arguments:, ast_node:, result:, result_name:, eager: false, runtime_state:, trace: true, &block)
|
|
881
907
|
if lazy?(lazy_obj)
|
|
882
|
-
orig_result = result
|
|
883
908
|
was_authorized_by_scope_items = runtime_state.was_authorized_by_scope_items
|
|
884
909
|
lazy = GraphQL::Execution::Lazy.new(field: field) do
|
|
885
910
|
# This block might be called in a new fiber;
|
|
@@ -889,13 +914,13 @@ module GraphQL
|
|
|
889
914
|
runtime_state.current_field = field
|
|
890
915
|
runtime_state.current_arguments = arguments
|
|
891
916
|
runtime_state.current_result_name = result_name
|
|
892
|
-
runtime_state.current_result =
|
|
917
|
+
runtime_state.current_result = result
|
|
893
918
|
runtime_state.was_authorized_by_scope_items = was_authorized_by_scope_items
|
|
894
919
|
# Wrap the execution of _this_ method with tracing,
|
|
895
920
|
# but don't wrap the continuation below
|
|
896
|
-
|
|
921
|
+
sync_result = nil
|
|
897
922
|
inner_obj = begin
|
|
898
|
-
|
|
923
|
+
sync_result = if trace
|
|
899
924
|
@current_trace.begin_execute_field(field, owner_object, arguments, query)
|
|
900
925
|
@current_trace.execute_field_lazy(field: field, query: query, object: owner_object, arguments: arguments, ast_node: ast_node) do
|
|
901
926
|
schema.sync_lazy(lazy_obj)
|
|
@@ -913,7 +938,7 @@ module GraphQL
|
|
|
913
938
|
end
|
|
914
939
|
ensure
|
|
915
940
|
if trace
|
|
916
|
-
@current_trace.end_execute_field(field, owner_object, arguments, query,
|
|
941
|
+
@current_trace.end_execute_field(field, owner_object, arguments, query, sync_result)
|
|
917
942
|
end
|
|
918
943
|
end
|
|
919
944
|
yield(inner_obj, runtime_state)
|
|
@@ -923,12 +948,7 @@ module GraphQL
|
|
|
923
948
|
lazy.value
|
|
924
949
|
else
|
|
925
950
|
set_result(result, result_name, lazy, false, false) # is_non_null is irrelevant here
|
|
926
|
-
|
|
927
|
-
while result
|
|
928
|
-
current_depth += 1
|
|
929
|
-
result = result.graphql_parent
|
|
930
|
-
end
|
|
931
|
-
@lazies_at_depth[current_depth] << lazy
|
|
951
|
+
@dataloader.lazy_at_depth(result.depth, lazy)
|
|
932
952
|
lazy
|
|
933
953
|
end
|
|
934
954
|
else
|
|
@@ -22,6 +22,7 @@ module GraphQL
|
|
|
22
22
|
# @param max_complexity [Integer, nil]
|
|
23
23
|
# @return [Array<GraphQL::Query::Result>] One result per query
|
|
24
24
|
def run_all(schema, query_options, context: {}, max_complexity: schema.max_complexity)
|
|
25
|
+
previous_multiplex = Fiber[:__graphql_current_multiplex]
|
|
25
26
|
queries = query_options.map do |opts|
|
|
26
27
|
query = case opts
|
|
27
28
|
when Hash
|
|
@@ -42,7 +43,6 @@ module GraphQL
|
|
|
42
43
|
trace.execute_multiplex(multiplex: multiplex) do
|
|
43
44
|
schema = multiplex.schema
|
|
44
45
|
queries = multiplex.queries
|
|
45
|
-
lazies_at_depth = Hash.new { |h, k| h[k] = [] }
|
|
46
46
|
multiplex_analyzers = schema.multiplex_analyzers
|
|
47
47
|
if multiplex.max_complexity
|
|
48
48
|
multiplex_analyzers += [GraphQL::Analysis::MaxQueryComplexity]
|
|
@@ -59,11 +59,6 @@ module GraphQL
|
|
|
59
59
|
# Do as much eager evaluation of the query as possible
|
|
60
60
|
results = []
|
|
61
61
|
queries.each_with_index do |query, idx|
|
|
62
|
-
if query.subscription? && !query.subscription_update?
|
|
63
|
-
subs_namespace = query.context.namespace(:subscriptions)
|
|
64
|
-
subs_namespace[:events] = []
|
|
65
|
-
subs_namespace[:subscriptions] = {}
|
|
66
|
-
end
|
|
67
62
|
multiplex.dataloader.append_job {
|
|
68
63
|
operation = query.selected_operation
|
|
69
64
|
result = if operation.nil? || !query.valid? || !query.context.errors.empty?
|
|
@@ -73,38 +68,27 @@ module GraphQL
|
|
|
73
68
|
# Although queries in a multiplex _share_ an Interpreter instance,
|
|
74
69
|
# they also have another item of state, which is private to that query
|
|
75
70
|
# in particular, assign it here:
|
|
76
|
-
runtime = Runtime.new(query: query
|
|
71
|
+
runtime = Runtime.new(query: query)
|
|
77
72
|
query.context.namespace(:interpreter_runtime)[:runtime] = runtime
|
|
78
|
-
|
|
73
|
+
if query.subscription? && !query.subscription_update?
|
|
74
|
+
schema.subscriptions.initialize_subscriptions(query)
|
|
75
|
+
end
|
|
79
76
|
query.current_trace.execute_query(query: query) do
|
|
80
77
|
runtime.run_eager
|
|
81
78
|
end
|
|
82
79
|
rescue GraphQL::ExecutionError => err
|
|
83
80
|
query.context.errors << err
|
|
84
|
-
NO_OPERATION
|
|
85
81
|
end
|
|
86
82
|
end
|
|
87
83
|
results[idx] = result
|
|
88
84
|
}
|
|
89
85
|
end
|
|
90
86
|
|
|
91
|
-
multiplex.dataloader.run
|
|
92
|
-
|
|
93
|
-
# Then, work through lazy results in a breadth-first way
|
|
94
|
-
multiplex.dataloader.append_job {
|
|
95
|
-
query = multiplex.queries.length == 1 ? multiplex.queries[0] : nil
|
|
96
|
-
multiplex.current_trace.execute_query_lazy(multiplex: multiplex, query: query) do
|
|
97
|
-
Interpreter::Resolve.resolve_each_depth(lazies_at_depth, multiplex.dataloader)
|
|
98
|
-
end
|
|
99
|
-
}
|
|
100
|
-
multiplex.dataloader.run
|
|
87
|
+
multiplex.dataloader.run(trace_query_lazy: multiplex)
|
|
101
88
|
|
|
102
89
|
# Then, find all errors and assign the result to the query object
|
|
103
90
|
results.each_with_index do |data_result, idx|
|
|
104
91
|
query = queries[idx]
|
|
105
|
-
if (events = query.context.namespace(:subscriptions)[:events]) && !events.empty?
|
|
106
|
-
schema.subscriptions.write_subscription(query, events)
|
|
107
|
-
end
|
|
108
92
|
# Assign the result so that it can be accessed in instrumentation
|
|
109
93
|
query.result_values = if data_result.equal?(NO_OPERATION)
|
|
110
94
|
if !query.valid? || !query.context.errors.empty?
|
|
@@ -114,6 +98,9 @@ module GraphQL
|
|
|
114
98
|
data_result
|
|
115
99
|
end
|
|
116
100
|
else
|
|
101
|
+
if query.subscription?
|
|
102
|
+
schema.subscriptions.finish_subscriptions(query)
|
|
103
|
+
end
|
|
117
104
|
result = {}
|
|
118
105
|
|
|
119
106
|
if !query.context.errors.empty?
|
|
@@ -133,13 +120,18 @@ module GraphQL
|
|
|
133
120
|
end
|
|
134
121
|
|
|
135
122
|
results
|
|
123
|
+
rescue SystemStackError => err
|
|
124
|
+
queries.map do |query|
|
|
125
|
+
schema.query_stack_error(query, err)
|
|
126
|
+
query.result_values ||= { "errors" => query.context.errors.map(&:to_h) }
|
|
127
|
+
query.result
|
|
128
|
+
end
|
|
136
129
|
rescue Exception
|
|
137
130
|
# TODO rescue at a higher level so it will catch errors in analysis, too
|
|
138
131
|
# Assign values here so that the query's `@executed` becomes true
|
|
139
132
|
queries.map { |q| q.result_values ||= {} }
|
|
140
133
|
raise
|
|
141
134
|
ensure
|
|
142
|
-
Fiber[:__graphql_current_multiplex] = nil
|
|
143
135
|
queries.map { |query|
|
|
144
136
|
runtime = query.context.namespace(:interpreter_runtime)[:runtime]
|
|
145
137
|
if runtime
|
|
@@ -148,6 +140,8 @@ module GraphQL
|
|
|
148
140
|
}
|
|
149
141
|
end
|
|
150
142
|
end
|
|
143
|
+
ensure
|
|
144
|
+
Fiber[:__graphql_current_multiplex] = previous_multiplex
|
|
151
145
|
end
|
|
152
146
|
end
|
|
153
147
|
|
|
@@ -38,7 +38,7 @@ module GraphQL
|
|
|
38
38
|
# (fewer clauses in a hot `case` block), but now it requires special handling here.
|
|
39
39
|
# I think it's still worth it for the performance win, but if the number of special
|
|
40
40
|
# cases grows, then maybe it's worth rethinking somehow.
|
|
41
|
-
if @value.is_a?(StandardError) &&
|
|
41
|
+
if @value.is_a?(StandardError) && !@value.is_a?(GraphQL::Execution::Skip)
|
|
42
42
|
raise @value
|
|
43
43
|
else
|
|
44
44
|
@value
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module GraphQL
|
|
3
|
+
module Execution
|
|
4
|
+
class LoadArgumentStep
|
|
5
|
+
def initialize(field_resolve_step:, arguments:, load_receiver:, argument_value:, argument_definition:, argument_key:)
|
|
6
|
+
@field_resolve_step = field_resolve_step
|
|
7
|
+
@load_receiver = load_receiver
|
|
8
|
+
@arguments = arguments
|
|
9
|
+
@argument_value = argument_value
|
|
10
|
+
@argument_definition = argument_definition
|
|
11
|
+
@argument_key = argument_key
|
|
12
|
+
@loaded_value = nil
|
|
13
|
+
@is_authorized = true
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def value
|
|
17
|
+
@field_resolve_step.set_current_field
|
|
18
|
+
schema = @field_resolve_step.runner.schema
|
|
19
|
+
@loaded_value = schema.sync_lazy(@loaded_value)
|
|
20
|
+
assign_value
|
|
21
|
+
rescue GraphQL::UnauthorizedError => auth_err
|
|
22
|
+
@is_authorized = false
|
|
23
|
+
schema.unauthorized_object(auth_err)
|
|
24
|
+
rescue GraphQL::RuntimeError => err
|
|
25
|
+
@loaded_value = if err.is_a?(Schema::Subscription::EarlyUnsubscribe)
|
|
26
|
+
err.unsubscribed_result
|
|
27
|
+
else
|
|
28
|
+
err
|
|
29
|
+
end
|
|
30
|
+
assign_value
|
|
31
|
+
rescue StandardError => stderr
|
|
32
|
+
begin
|
|
33
|
+
@field_resolve_step.selections_step.query.handle_or_reraise(stderr, field: @field_definition, arguments: @arguments, object: nil)
|
|
34
|
+
rescue GraphQL::ExecutionError => ex_err
|
|
35
|
+
@loaded_value = ex_err
|
|
36
|
+
end
|
|
37
|
+
assign_value
|
|
38
|
+
ensure
|
|
39
|
+
@field_resolve_step.set_current_field(nil)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def call
|
|
43
|
+
@field_resolve_step.set_current_field
|
|
44
|
+
context = @field_resolve_step.selections_step.query.context
|
|
45
|
+
@loaded_value = begin
|
|
46
|
+
@load_receiver.load_and_authorize_application_object(@argument_definition, @argument_value, context)
|
|
47
|
+
rescue GraphQL::UnauthorizedError => auth_err
|
|
48
|
+
@is_authorized = false
|
|
49
|
+
context.schema.unauthorized_object(auth_err)
|
|
50
|
+
end
|
|
51
|
+
if (runner = @field_resolve_step.runner).resolves_lazies && runner.lazy?(@loaded_value)
|
|
52
|
+
runner.dataloader.lazy_at_depth(@field_resolve_step.path.size, self)
|
|
53
|
+
else
|
|
54
|
+
assign_value
|
|
55
|
+
end
|
|
56
|
+
rescue GraphQL::RuntimeError => err
|
|
57
|
+
@loaded_value = if err.is_a?(Schema::Subscription::EarlyUnsubscribe)
|
|
58
|
+
@is_authorized = false
|
|
59
|
+
err.unsubscribed_result
|
|
60
|
+
else
|
|
61
|
+
err
|
|
62
|
+
end
|
|
63
|
+
assign_value
|
|
64
|
+
rescue StandardError => stderr
|
|
65
|
+
@loaded_value = begin
|
|
66
|
+
context.query.handle_or_reraise(stderr, field: @field_resolve_step.field_definition, arguments: @field_resolve_step.arguments, object: nil) # rubocop:disable Development/ContextIsPassedCop
|
|
67
|
+
rescue GraphQL::ExecutionError => ex_err
|
|
68
|
+
ex_err
|
|
69
|
+
end
|
|
70
|
+
assign_value
|
|
71
|
+
ensure
|
|
72
|
+
@field_resolve_step.set_current_field(nil)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def assign_value
|
|
78
|
+
if @loaded_value.is_a?(GraphQL::RuntimeError)
|
|
79
|
+
@loaded_value.path = @field_resolve_step.path
|
|
80
|
+
@field_resolve_step.arguments = @loaded_value
|
|
81
|
+
elsif @is_authorized == false
|
|
82
|
+
# An unauthorized_object hook ate the error
|
|
83
|
+
@field_resolve_step.arguments = EmptyObjects::EMPTY_HASH
|
|
84
|
+
field_pending_steps = @field_resolve_step.pending_steps
|
|
85
|
+
field_pending_steps.clear
|
|
86
|
+
@field_resolve_step.build_errors_result(nil, nil)
|
|
87
|
+
return
|
|
88
|
+
else
|
|
89
|
+
query = @field_resolve_step.selections_step.query
|
|
90
|
+
query.current_trace.object_loaded(@argument_definition, @loaded_value, query.context)
|
|
91
|
+
@arguments[@argument_key] = @loaded_value
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
field_pending_steps = @field_resolve_step.pending_steps
|
|
95
|
+
field_pending_steps.delete(self)
|
|
96
|
+
if @field_resolve_step.arguments && field_pending_steps.size == 0 # rubocop:disable Development/ContextIsPassedCop
|
|
97
|
+
@field_resolve_step.runner.add_step(@field_resolve_step)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -33,7 +33,7 @@ module GraphQL
|
|
|
33
33
|
@queries.each { |q| q.multiplex = self }
|
|
34
34
|
@context = context
|
|
35
35
|
@dataloader = @context[:dataloader] ||= @schema.dataloader_class.new
|
|
36
|
-
@tracers = schema.tracers + (context[:tracers] ||
|
|
36
|
+
@tracers = schema.tracers + (context[:tracers] || EmptyObjects::EMPTY_ARRAY)
|
|
37
37
|
@max_complexity = max_complexity
|
|
38
38
|
@current_trace = context[:trace] ||= schema.new_trace(multiplex: self)
|
|
39
39
|
@logger = nil
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "graphql/execution/prepare_object_step"
|
|
3
|
+
require "graphql/execution/input_values"
|
|
4
|
+
require "graphql/execution/field_resolve_step"
|
|
5
|
+
require "graphql/execution/finalize"
|
|
6
|
+
require "graphql/execution/load_argument_step"
|
|
7
|
+
require "graphql/execution/resolve_type_step"
|
|
8
|
+
require "graphql/execution/runner"
|
|
9
|
+
require "graphql/execution/selections_step"
|
|
10
|
+
module GraphQL
|
|
11
|
+
module Execution
|
|
12
|
+
module Finalizer
|
|
13
|
+
attr_accessor :path
|
|
14
|
+
|
|
15
|
+
def finalize_graphql_result(query, result_data, result_key)
|
|
16
|
+
raise RequiredImplementationMissingError, "#{self.class} must implement #finalize_graphql_result(query, result_data, result_key)\n\nresult_data: #{result_data}\nresult_key: #{result_key.inspect}"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def ast_node
|
|
20
|
+
ast_nodes&.first
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def ast_node=(new_node)
|
|
24
|
+
@ast_nodes = [new_node]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
attr_accessor :ast_nodes
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
module HaltExecution
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
module PostProcessor
|
|
34
|
+
def after_resolve(field_results)
|
|
35
|
+
raise RequiredImplementationMissingError, "#{self.class}#after_resolve should handle `field_results` and return a new value to use"
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
module Next
|
|
40
|
+
module SchemaExtension
|
|
41
|
+
def execute_next(query_str = nil, query: nil, subscription_topic: nil, context: nil, document: nil, operation_name: nil, variables: nil, warden: nil, root_value: nil, validate: true, visibility_profile: nil)
|
|
42
|
+
multiplex_context = if context
|
|
43
|
+
{
|
|
44
|
+
backtrace: context[:backtrace],
|
|
45
|
+
tracers: context[:tracers],
|
|
46
|
+
trace: context[:trace],
|
|
47
|
+
dataloader: context[:dataloader],
|
|
48
|
+
trace_mode: context[:trace_mode],
|
|
49
|
+
}
|
|
50
|
+
else
|
|
51
|
+
{}
|
|
52
|
+
end
|
|
53
|
+
query_opts = {
|
|
54
|
+
query: query || query_str,
|
|
55
|
+
subscription_topic: subscription_topic,
|
|
56
|
+
document: document,
|
|
57
|
+
context: context,
|
|
58
|
+
validate: validate,
|
|
59
|
+
variables: variables,
|
|
60
|
+
root_value: root_value,
|
|
61
|
+
operation_name: operation_name,
|
|
62
|
+
visibility_profile: visibility_profile,
|
|
63
|
+
warden: warden,
|
|
64
|
+
}
|
|
65
|
+
m_results = multiplex_next([query_opts], context: multiplex_context, max_complexity: nil)
|
|
66
|
+
m_results[0]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def multiplex_next(query_options, context: {}, max_complexity: self.max_complexity)
|
|
70
|
+
Next.run_all(self, query_options, context: context, max_complexity: max_complexity)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def self.use(schema, as_default: false)
|
|
75
|
+
schema.extend(SchemaExtension)
|
|
76
|
+
schema.default_execution_next(as_default)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def self.run_all(schema, query_options, context: {}, max_complexity: schema.max_complexity)
|
|
80
|
+
queries = query_options.map do |opts|
|
|
81
|
+
query = case opts
|
|
82
|
+
when Hash
|
|
83
|
+
schema.query_class.new(schema, nil, **opts)
|
|
84
|
+
when GraphQL::Query, GraphQL::Query::Partial
|
|
85
|
+
opts
|
|
86
|
+
else
|
|
87
|
+
raise "Expected Hash or GraphQL::Query, not #{opts.class} (#{opts.inspect})"
|
|
88
|
+
end
|
|
89
|
+
query.context[:__graphql_execute_next] = true
|
|
90
|
+
query
|
|
91
|
+
end
|
|
92
|
+
multiplex = Execution::Multiplex.new(schema: schema, queries: queries, context: context, max_complexity: max_complexity)
|
|
93
|
+
runner = Runner.new(multiplex)
|
|
94
|
+
runner.execute
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|