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