graphql 1.12.10 → 1.13.4
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/core.rb +3 -1
- data/lib/generators/graphql/install_generator.rb +9 -2
- data/lib/generators/graphql/mutation_generator.rb +1 -1
- data/lib/generators/graphql/object_generator.rb +2 -1
- data/lib/generators/graphql/relay.rb +19 -11
- data/lib/generators/graphql/templates/schema.erb +14 -2
- data/lib/generators/graphql/type_generator.rb +0 -1
- data/lib/graphql/analysis/ast/field_usage.rb +28 -1
- data/lib/graphql/analysis/ast/query_complexity.rb +10 -14
- data/lib/graphql/analysis/ast/visitor.rb +4 -4
- data/lib/graphql/backtrace/table.rb +15 -3
- data/lib/graphql/backtrace/tracer.rb +7 -4
- data/lib/graphql/base_type.rb +4 -2
- data/lib/graphql/boolean_type.rb +1 -1
- data/lib/graphql/dataloader/null_dataloader.rb +1 -0
- data/lib/graphql/dataloader/source.rb +50 -2
- data/lib/graphql/dataloader.rb +110 -41
- data/lib/graphql/define/instance_definable.rb +1 -1
- data/lib/graphql/deprecated_dsl.rb +11 -3
- data/lib/graphql/deprecation.rb +1 -5
- data/lib/graphql/directive/deprecated_directive.rb +1 -1
- data/lib/graphql/directive/include_directive.rb +1 -1
- data/lib/graphql/directive/skip_directive.rb +1 -1
- data/lib/graphql/directive.rb +0 -4
- data/lib/graphql/enum_type.rb +5 -1
- data/lib/graphql/execution/errors.rb +1 -0
- data/lib/graphql/execution/execute.rb +1 -1
- data/lib/graphql/execution/interpreter/arguments.rb +1 -1
- data/lib/graphql/execution/interpreter/arguments_cache.rb +5 -4
- data/lib/graphql/execution/interpreter/resolve.rb +6 -2
- data/lib/graphql/execution/interpreter/runtime.rb +513 -213
- data/lib/graphql/execution/interpreter.rb +4 -8
- data/lib/graphql/execution/lazy.rb +5 -1
- data/lib/graphql/execution/lookahead.rb +2 -2
- data/lib/graphql/execution/multiplex.rb +4 -1
- data/lib/graphql/float_type.rb +1 -1
- data/lib/graphql/id_type.rb +1 -1
- data/lib/graphql/int_type.rb +1 -1
- data/lib/graphql/integer_encoding_error.rb +18 -2
- data/lib/graphql/introspection/directive_type.rb +1 -1
- data/lib/graphql/introspection/entry_points.rb +2 -2
- data/lib/graphql/introspection/enum_value_type.rb +2 -2
- data/lib/graphql/introspection/field_type.rb +2 -2
- data/lib/graphql/introspection/input_value_type.rb +10 -4
- data/lib/graphql/introspection/schema_type.rb +3 -3
- data/lib/graphql/introspection/type_type.rb +10 -10
- data/lib/graphql/language/block_string.rb +2 -6
- data/lib/graphql/language/document_from_schema_definition.rb +10 -4
- data/lib/graphql/language/lexer.rb +0 -3
- data/lib/graphql/language/lexer.rl +0 -4
- data/lib/graphql/language/nodes.rb +13 -3
- data/lib/graphql/language/parser.rb +442 -434
- data/lib/graphql/language/parser.y +5 -4
- data/lib/graphql/language/printer.rb +6 -1
- data/lib/graphql/language/sanitized_printer.rb +5 -5
- data/lib/graphql/language/token.rb +0 -4
- data/lib/graphql/name_validator.rb +0 -4
- data/lib/graphql/pagination/active_record_relation_connection.rb +43 -6
- data/lib/graphql/pagination/connections.rb +40 -16
- data/lib/graphql/pagination/relation_connection.rb +57 -27
- data/lib/graphql/query/arguments.rb +1 -1
- data/lib/graphql/query/arguments_cache.rb +1 -1
- data/lib/graphql/query/context.rb +15 -2
- data/lib/graphql/query/literal_input.rb +1 -1
- data/lib/graphql/query/null_context.rb +12 -7
- data/lib/graphql/query/serial_execution/field_resolution.rb +1 -1
- data/lib/graphql/query/validation_pipeline.rb +1 -1
- data/lib/graphql/query/variables.rb +5 -1
- data/lib/graphql/query.rb +5 -1
- data/lib/graphql/relay/edges_instrumentation.rb +0 -1
- data/lib/graphql/relay/global_id_resolve.rb +1 -1
- data/lib/graphql/relay/page_info.rb +1 -1
- data/lib/graphql/rubocop/graphql/base_cop.rb +36 -0
- data/lib/graphql/rubocop/graphql/default_null_true.rb +43 -0
- data/lib/graphql/rubocop/graphql/default_required_true.rb +43 -0
- data/lib/graphql/rubocop.rb +4 -0
- data/lib/graphql/schema/addition.rb +247 -0
- data/lib/graphql/schema/argument.rb +103 -45
- data/lib/graphql/schema/build_from_definition.rb +13 -7
- data/lib/graphql/schema/directive/feature.rb +1 -1
- data/lib/graphql/schema/directive/flagged.rb +2 -2
- data/lib/graphql/schema/directive/include.rb +1 -1
- data/lib/graphql/schema/directive/skip.rb +1 -1
- data/lib/graphql/schema/directive/transform.rb +14 -2
- data/lib/graphql/schema/directive.rb +7 -3
- data/lib/graphql/schema/enum.rb +70 -11
- data/lib/graphql/schema/enum_value.rb +6 -0
- data/lib/graphql/schema/field/connection_extension.rb +1 -1
- data/lib/graphql/schema/field.rb +243 -81
- data/lib/graphql/schema/field_extension.rb +89 -2
- data/lib/graphql/schema/find_inherited_value.rb +1 -0
- data/lib/graphql/schema/finder.rb +5 -5
- data/lib/graphql/schema/input_object.rb +39 -29
- data/lib/graphql/schema/interface.rb +11 -20
- data/lib/graphql/schema/introspection_system.rb +1 -1
- data/lib/graphql/schema/list.rb +3 -1
- data/lib/graphql/schema/member/accepts_definition.rb +15 -3
- data/lib/graphql/schema/member/build_type.rb +1 -4
- data/lib/graphql/schema/member/cached_graphql_definition.rb +29 -2
- data/lib/graphql/schema/member/has_arguments.rb +145 -57
- data/lib/graphql/schema/member/has_deprecation_reason.rb +1 -1
- data/lib/graphql/schema/member/has_fields.rb +76 -18
- data/lib/graphql/schema/member/has_interfaces.rb +90 -0
- data/lib/graphql/schema/member.rb +1 -0
- data/lib/graphql/schema/non_null.rb +7 -1
- data/lib/graphql/schema/object.rb +10 -75
- data/lib/graphql/schema/printer.rb +12 -17
- data/lib/graphql/schema/relay_classic_mutation.rb +37 -3
- data/lib/graphql/schema/resolver/has_payload_type.rb +27 -2
- data/lib/graphql/schema/resolver.rb +75 -65
- data/lib/graphql/schema/scalar.rb +2 -0
- data/lib/graphql/schema/subscription.rb +36 -8
- data/lib/graphql/schema/traversal.rb +1 -1
- data/lib/graphql/schema/type_expression.rb +1 -1
- data/lib/graphql/schema/type_membership.rb +18 -4
- data/lib/graphql/schema/union.rb +8 -1
- data/lib/graphql/schema/validator/allow_blank_validator.rb +29 -0
- data/lib/graphql/schema/validator/allow_null_validator.rb +26 -0
- data/lib/graphql/schema/validator/exclusion_validator.rb +3 -1
- data/lib/graphql/schema/validator/format_validator.rb +4 -5
- data/lib/graphql/schema/validator/inclusion_validator.rb +3 -1
- data/lib/graphql/schema/validator/length_validator.rb +5 -3
- data/lib/graphql/schema/validator/numericality_validator.rb +13 -2
- data/lib/graphql/schema/validator/required_validator.rb +29 -15
- data/lib/graphql/schema/validator.rb +33 -25
- data/lib/graphql/schema/warden.rb +116 -52
- data/lib/graphql/schema.rb +162 -227
- data/lib/graphql/static_validation/all_rules.rb +1 -0
- data/lib/graphql/static_validation/base_visitor.rb +8 -5
- data/lib/graphql/static_validation/definition_dependencies.rb +0 -1
- data/lib/graphql/static_validation/error.rb +3 -1
- data/lib/graphql/static_validation/literal_validator.rb +1 -1
- data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +1 -1
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +52 -26
- data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +25 -4
- data/lib/graphql/static_validation/rules/fragments_are_finite.rb +2 -2
- data/lib/graphql/static_validation/rules/query_root_exists.rb +17 -0
- data/lib/graphql/static_validation/rules/query_root_exists_error.rb +26 -0
- data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +3 -1
- data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +4 -4
- data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +13 -7
- data/lib/graphql/static_validation/validation_context.rb +8 -2
- data/lib/graphql/static_validation/validator.rb +15 -12
- data/lib/graphql/string_encoding_error.rb +13 -3
- data/lib/graphql/string_type.rb +1 -1
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +36 -6
- data/lib/graphql/subscriptions/event.rb +68 -31
- data/lib/graphql/subscriptions/serialize.rb +23 -3
- data/lib/graphql/subscriptions.rb +17 -19
- data/lib/graphql/tracing/active_support_notifications_tracing.rb +6 -20
- data/lib/graphql/tracing/appsignal_tracing.rb +15 -0
- data/lib/graphql/tracing/notifications_tracing.rb +59 -0
- data/lib/graphql/types/big_int.rb +5 -1
- data/lib/graphql/types/int.rb +1 -1
- data/lib/graphql/types/relay/connection_behaviors.rb +26 -9
- data/lib/graphql/types/relay/default_relay.rb +5 -1
- data/lib/graphql/types/relay/edge_behaviors.rb +13 -2
- data/lib/graphql/types/relay/has_node_field.rb +2 -2
- data/lib/graphql/types/relay/has_nodes_field.rb +2 -2
- data/lib/graphql/types/relay/node_field.rb +15 -4
- data/lib/graphql/types/relay/nodes_field.rb +14 -4
- data/lib/graphql/types/string.rb +1 -1
- data/lib/graphql/unauthorized_error.rb +1 -1
- data/lib/graphql/version.rb +1 -1
- data/lib/graphql.rb +10 -28
- data/readme.md +1 -4
- metadata +17 -21
- data/lib/graphql/execution/interpreter/hash_response.rb +0 -46
|
@@ -8,6 +8,137 @@ module GraphQL
|
|
|
8
8
|
#
|
|
9
9
|
# @api private
|
|
10
10
|
class Runtime
|
|
11
|
+
|
|
12
|
+
module GraphQLResult
|
|
13
|
+
def initialize(result_name, parent_result)
|
|
14
|
+
@graphql_parent = parent_result
|
|
15
|
+
if parent_result && parent_result.graphql_dead
|
|
16
|
+
@graphql_dead = true
|
|
17
|
+
end
|
|
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
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
attr_accessor :graphql_dead
|
|
24
|
+
attr_reader :graphql_parent, :graphql_result_name
|
|
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 = {}
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
include GraphQLResult
|
|
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
|
|
140
|
+
end
|
|
141
|
+
|
|
11
142
|
# @return [GraphQL::Query]
|
|
12
143
|
attr_reader :query
|
|
13
144
|
|
|
@@ -17,30 +148,48 @@ module GraphQL
|
|
|
17
148
|
# @return [GraphQL::Query::Context]
|
|
18
149
|
attr_reader :context
|
|
19
150
|
|
|
20
|
-
def initialize(query
|
|
151
|
+
def initialize(query:)
|
|
21
152
|
@query = query
|
|
22
153
|
@dataloader = query.multiplex.dataloader
|
|
23
154
|
@schema = query.schema
|
|
24
155
|
@context = query.context
|
|
25
156
|
@multiplex_context = query.multiplex.context
|
|
26
157
|
@interpreter_context = @context.namespace(:interpreter)
|
|
27
|
-
@response =
|
|
28
|
-
|
|
29
|
-
@
|
|
158
|
+
@response = GraphQLResultHash.new(nil, nil)
|
|
159
|
+
# Identify runtime directives by checking which of this schema's directives have overridden `def self.resolve`
|
|
160
|
+
@runtime_directive_names = []
|
|
161
|
+
noop_resolve_owner = GraphQL::Schema::Directive.singleton_class
|
|
162
|
+
schema.directives.each do |name, dir_defn|
|
|
163
|
+
if dir_defn.method(:resolve).owner != noop_resolve_owner
|
|
164
|
+
@runtime_directive_names << name
|
|
165
|
+
end
|
|
166
|
+
end
|
|
30
167
|
# A cache of { Class => { String => Schema::Field } }
|
|
31
168
|
# Which assumes that MyObject.get_field("myField") will return the same field
|
|
32
169
|
# during the lifetime of a query
|
|
33
170
|
@fields_cache = Hash.new { |h, k| h[k] = {} }
|
|
171
|
+
# { Class => Boolean }
|
|
172
|
+
@lazy_cache = {}
|
|
34
173
|
end
|
|
35
174
|
|
|
36
|
-
def
|
|
37
|
-
@response.
|
|
175
|
+
def final_result
|
|
176
|
+
@response && @response.graphql_result_data
|
|
38
177
|
end
|
|
39
178
|
|
|
40
179
|
def inspect
|
|
41
180
|
"#<#{self.class.name} response=#{@response.inspect}>"
|
|
42
181
|
end
|
|
43
182
|
|
|
183
|
+
def tap_or_each(obj_or_array)
|
|
184
|
+
if obj_or_array.is_a?(Array)
|
|
185
|
+
obj_or_array.each do |item|
|
|
186
|
+
yield(item, true)
|
|
187
|
+
end
|
|
188
|
+
else
|
|
189
|
+
yield(obj_or_array, false)
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
44
193
|
# This _begins_ the execution. Some deferred work
|
|
45
194
|
# might be stored up in lazies.
|
|
46
195
|
# @return [void]
|
|
@@ -55,21 +204,43 @@ module GraphQL
|
|
|
55
204
|
|
|
56
205
|
if object_proxy.nil?
|
|
57
206
|
# Root .authorized? returned false.
|
|
58
|
-
|
|
207
|
+
@response = nil
|
|
59
208
|
else
|
|
60
|
-
resolve_with_directives(object_proxy, root_operation) do # execute query level directives
|
|
209
|
+
resolve_with_directives(object_proxy, root_operation.directives) do # execute query level directives
|
|
61
210
|
gathered_selections = gather_selections(object_proxy, root_type, root_operation.selections)
|
|
62
|
-
#
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
211
|
+
# This is kind of a hack -- `gathered_selections` is an Array if any of the selections
|
|
212
|
+
# require isolation during execution (because of runtime directives). In that case,
|
|
213
|
+
# make a new, isolated result hash for writing the result into. (That isolated response
|
|
214
|
+
# is eventually merged back into the main response)
|
|
215
|
+
#
|
|
216
|
+
# Otherwise, `gathered_selections` is a hash of selections which can be
|
|
217
|
+
# directly evaluated and the results can be written right into the main response hash.
|
|
218
|
+
tap_or_each(gathered_selections) do |selections, is_selection_array|
|
|
219
|
+
if is_selection_array
|
|
220
|
+
selection_response = GraphQLResultHash.new(nil, nil)
|
|
221
|
+
final_response = @response
|
|
222
|
+
else
|
|
223
|
+
selection_response = @response
|
|
224
|
+
final_response = nil
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
@dataloader.append_job {
|
|
228
|
+
set_all_interpreter_context(query.root_value, nil, nil, path)
|
|
229
|
+
resolve_with_directives(object_proxy, selections.graphql_directives) do
|
|
230
|
+
evaluate_selections(
|
|
231
|
+
path,
|
|
232
|
+
context.scoped_context,
|
|
233
|
+
object_proxy,
|
|
234
|
+
root_type,
|
|
235
|
+
root_op_type == "mutation",
|
|
236
|
+
selections,
|
|
237
|
+
selection_response,
|
|
238
|
+
final_response,
|
|
239
|
+
nil,
|
|
240
|
+
)
|
|
241
|
+
end
|
|
242
|
+
}
|
|
243
|
+
end
|
|
73
244
|
end
|
|
74
245
|
end
|
|
75
246
|
delete_interpreter_context(:current_path)
|
|
@@ -79,15 +250,36 @@ module GraphQL
|
|
|
79
250
|
nil
|
|
80
251
|
end
|
|
81
252
|
|
|
82
|
-
|
|
253
|
+
# @return [void]
|
|
254
|
+
def deep_merge_selection_result(from_result, into_result)
|
|
255
|
+
from_result.each do |key, value|
|
|
256
|
+
if !into_result.key?(key)
|
|
257
|
+
into_result[key] = value
|
|
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
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
from_result.graphql_merged_into = into_result
|
|
272
|
+
nil
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def gather_selections(owner_object, owner_type, selections, selections_to_run = nil, selections_by_name = GraphQLSelectionSet.new)
|
|
83
276
|
selections.each do |node|
|
|
84
277
|
# Skip gathering this if the directive says so
|
|
85
278
|
if !directives_include?(node, owner_object, owner_type)
|
|
86
279
|
next
|
|
87
280
|
end
|
|
88
281
|
|
|
89
|
-
|
|
90
|
-
when GraphQL::Language::Nodes::Field
|
|
282
|
+
if node.is_a?(GraphQL::Language::Nodes::Field)
|
|
91
283
|
response_key = node.alias || node.name
|
|
92
284
|
selections = selections_by_name[response_key]
|
|
93
285
|
# if there was already a selection of this field,
|
|
@@ -103,58 +295,84 @@ module GraphQL
|
|
|
103
295
|
# No selection was found for this field yet
|
|
104
296
|
selections_by_name[response_key] = node
|
|
105
297
|
end
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
298
|
+
else
|
|
299
|
+
# This is an InlineFragment or a FragmentSpread
|
|
300
|
+
if @runtime_directive_names.any? && node.directives.any? { |d| @runtime_directive_names.include?(d.name) }
|
|
301
|
+
next_selections = GraphQLSelectionSet.new
|
|
302
|
+
next_selections.graphql_directives = node.directives
|
|
303
|
+
if selections_to_run
|
|
304
|
+
selections_to_run << next_selections
|
|
305
|
+
else
|
|
306
|
+
selections_to_run = []
|
|
307
|
+
selections_to_run << selections_by_name
|
|
308
|
+
selections_to_run << next_selections
|
|
309
|
+
end
|
|
310
|
+
else
|
|
311
|
+
next_selections = selections_by_name
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
case node
|
|
315
|
+
when GraphQL::Language::Nodes::InlineFragment
|
|
316
|
+
if node.type
|
|
317
|
+
type_defn = schema.get_type(node.type.name, context)
|
|
318
|
+
|
|
319
|
+
# Faster than .map{}.include?()
|
|
320
|
+
query.warden.possible_types(type_defn).each do |t|
|
|
321
|
+
if t == owner_type
|
|
322
|
+
gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
|
|
323
|
+
break
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
else
|
|
327
|
+
# it's an untyped fragment, definitely continue
|
|
328
|
+
gather_selections(owner_object, owner_type, node.selections, selections_to_run, next_selections)
|
|
329
|
+
end
|
|
330
|
+
when GraphQL::Language::Nodes::FragmentSpread
|
|
331
|
+
fragment_def = query.fragments[node.name]
|
|
332
|
+
type_defn = query.get_type(fragment_def.type.name)
|
|
333
|
+
possible_types = query.warden.possible_types(type_defn)
|
|
334
|
+
possible_types.each do |t|
|
|
111
335
|
if t == owner_type
|
|
112
|
-
gather_selections(owner_object, owner_type,
|
|
336
|
+
gather_selections(owner_object, owner_type, fragment_def.selections, selections_to_run, next_selections)
|
|
113
337
|
break
|
|
114
338
|
end
|
|
115
339
|
end
|
|
116
340
|
else
|
|
117
|
-
|
|
118
|
-
gather_selections(owner_object, owner_type, node.selections, selections_by_name)
|
|
341
|
+
raise "Invariant: unexpected selection class: #{node.class}"
|
|
119
342
|
end
|
|
120
|
-
when GraphQL::Language::Nodes::FragmentSpread
|
|
121
|
-
fragment_def = query.fragments[node.name]
|
|
122
|
-
type_defn = schema.get_type(fragment_def.type.name)
|
|
123
|
-
possible_types = query.warden.possible_types(type_defn)
|
|
124
|
-
possible_types.each do |t|
|
|
125
|
-
if t == owner_type
|
|
126
|
-
gather_selections(owner_object, owner_type, fragment_def.selections, selections_by_name)
|
|
127
|
-
break
|
|
128
|
-
end
|
|
129
|
-
end
|
|
130
|
-
else
|
|
131
|
-
raise "Invariant: unexpected selection class: #{node.class}"
|
|
132
343
|
end
|
|
133
344
|
end
|
|
134
|
-
selections_by_name
|
|
345
|
+
selections_to_run || selections_by_name
|
|
135
346
|
end
|
|
136
347
|
|
|
137
348
|
NO_ARGS = {}.freeze
|
|
138
349
|
|
|
139
350
|
# @return [void]
|
|
140
|
-
def evaluate_selections(path, scoped_context, owner_object, owner_type, is_eager_selection, gathered_selections)
|
|
351
|
+
def evaluate_selections(path, scoped_context, owner_object, owner_type, is_eager_selection, gathered_selections, selections_result, target_result, parent_object) # rubocop:disable Metrics/ParameterLists
|
|
141
352
|
set_all_interpreter_context(owner_object, nil, nil, path)
|
|
142
353
|
|
|
354
|
+
finished_jobs = 0
|
|
355
|
+
enqueued_jobs = gathered_selections.size
|
|
143
356
|
gathered_selections.each do |result_name, field_ast_nodes_or_ast_node|
|
|
144
357
|
@dataloader.append_job {
|
|
145
358
|
evaluate_selection(
|
|
146
|
-
path, result_name, field_ast_nodes_or_ast_node, scoped_context, owner_object, owner_type, is_eager_selection
|
|
359
|
+
path, result_name, field_ast_nodes_or_ast_node, scoped_context, owner_object, owner_type, is_eager_selection, selections_result, parent_object
|
|
147
360
|
)
|
|
361
|
+
finished_jobs += 1
|
|
362
|
+
if target_result && finished_jobs == enqueued_jobs
|
|
363
|
+
deep_merge_selection_result(selections_result, target_result)
|
|
364
|
+
end
|
|
148
365
|
}
|
|
149
366
|
end
|
|
150
367
|
|
|
151
|
-
|
|
368
|
+
selections_result
|
|
152
369
|
end
|
|
153
370
|
|
|
154
371
|
attr_reader :progress_path
|
|
155
372
|
|
|
156
373
|
# @return [void]
|
|
157
|
-
def evaluate_selection(path, result_name, field_ast_nodes_or_ast_node, scoped_context, owner_object, owner_type, is_eager_field)
|
|
374
|
+
def evaluate_selection(path, result_name, field_ast_nodes_or_ast_node, scoped_context, owner_object, owner_type, is_eager_field, selections_result, parent_object) # rubocop:disable Metrics/ParameterLists
|
|
375
|
+
return if dead_result?(selections_result)
|
|
158
376
|
# As a performance optimization, the hash key will be a `Node` if
|
|
159
377
|
# there's only one selection of the field. But if there are multiple
|
|
160
378
|
# selections of the field, it will be an Array of nodes
|
|
@@ -166,7 +384,9 @@ module GraphQL
|
|
|
166
384
|
ast_node = field_ast_nodes_or_ast_node
|
|
167
385
|
end
|
|
168
386
|
field_name = ast_node.name
|
|
169
|
-
|
|
387
|
+
# This can't use `query.get_field` because it gets confused on introspection below if `field_defn` isn't `nil`,
|
|
388
|
+
# because of how `is_introspection` is used to call `.authorized_new` later on.
|
|
389
|
+
field_defn = @fields_cache[owner_type][field_name] ||= owner_type.get_field(field_name, @context)
|
|
170
390
|
is_introspection = false
|
|
171
391
|
if field_defn.nil?
|
|
172
392
|
field_defn = if owner_type == schema.query && (entry_point_field = schema.introspection_system.entry_point(name: field_name))
|
|
@@ -188,7 +408,9 @@ module GraphQL
|
|
|
188
408
|
# This seems janky, but we need to know
|
|
189
409
|
# the field's return type at this path in order
|
|
190
410
|
# to propagate `null`
|
|
191
|
-
|
|
411
|
+
if return_type.non_null?
|
|
412
|
+
(selections_result.graphql_non_null_field_names ||= []).push(result_name)
|
|
413
|
+
end
|
|
192
414
|
# Set this before calling `run_with_directives`, so that the directive can have the latest path
|
|
193
415
|
set_all_interpreter_context(nil, field_defn, nil, next_path)
|
|
194
416
|
|
|
@@ -199,24 +421,24 @@ module GraphQL
|
|
|
199
421
|
object = authorized_new(field_defn.owner, object, context)
|
|
200
422
|
end
|
|
201
423
|
|
|
202
|
-
total_args_count = field_defn.arguments.size
|
|
424
|
+
total_args_count = field_defn.arguments(context).size
|
|
203
425
|
if total_args_count == 0
|
|
204
|
-
|
|
205
|
-
evaluate_selection_with_args(
|
|
426
|
+
resolved_arguments = GraphQL::Execution::Interpreter::Arguments::EMPTY
|
|
427
|
+
evaluate_selection_with_args(resolved_arguments, field_defn, next_path, ast_node, field_ast_nodes, scoped_context, owner_type, object, is_eager_field, result_name, selections_result, parent_object)
|
|
206
428
|
else
|
|
207
429
|
# TODO remove all arguments(...) usages?
|
|
208
430
|
@query.arguments_cache.dataload_for(ast_node, field_defn, object) do |resolved_arguments|
|
|
209
|
-
evaluate_selection_with_args(resolved_arguments, field_defn, next_path, ast_node, field_ast_nodes, scoped_context, owner_type, object, is_eager_field)
|
|
431
|
+
evaluate_selection_with_args(resolved_arguments, field_defn, next_path, ast_node, field_ast_nodes, scoped_context, owner_type, object, is_eager_field, result_name, selections_result, parent_object)
|
|
210
432
|
end
|
|
211
433
|
end
|
|
212
434
|
end
|
|
213
435
|
|
|
214
|
-
def evaluate_selection_with_args(
|
|
436
|
+
def evaluate_selection_with_args(arguments, field_defn, next_path, ast_node, field_ast_nodes, scoped_context, owner_type, object, is_eager_field, result_name, selection_result, parent_object) # rubocop:disable Metrics/ParameterLists
|
|
215
437
|
context.scoped_context = scoped_context
|
|
216
438
|
return_type = field_defn.type
|
|
217
|
-
after_lazy(
|
|
439
|
+
after_lazy(arguments, owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, scoped_context: context.scoped_context, owner_object: object, arguments: arguments, result_name: result_name, result: selection_result) do |resolved_arguments|
|
|
218
440
|
if resolved_arguments.is_a?(GraphQL::ExecutionError) || resolved_arguments.is_a?(GraphQL::UnauthorizedError)
|
|
219
|
-
continue_value(next_path, resolved_arguments, owner_type, field_defn, return_type.non_null?, ast_node)
|
|
441
|
+
continue_value(next_path, resolved_arguments, owner_type, field_defn, return_type.non_null?, ast_node, result_name, selection_result)
|
|
220
442
|
next
|
|
221
443
|
end
|
|
222
444
|
|
|
@@ -253,6 +475,8 @@ module GraphQL
|
|
|
253
475
|
# This is used by `__typename` in order to support the legacy runtime,
|
|
254
476
|
# but it has no use here (and it's always `nil`).
|
|
255
477
|
# Stop adding it here to avoid the overhead of `.merge_extras` below.
|
|
478
|
+
when :parent
|
|
479
|
+
extra_args[:parent] = parent_object
|
|
256
480
|
else
|
|
257
481
|
extra_args[extra] = field_defn.fetch_extra(extra, context)
|
|
258
482
|
end
|
|
@@ -263,17 +487,22 @@ module GraphQL
|
|
|
263
487
|
resolved_arguments.keyword_arguments
|
|
264
488
|
end
|
|
265
489
|
|
|
266
|
-
set_all_interpreter_context(nil, nil,
|
|
490
|
+
set_all_interpreter_context(nil, nil, resolved_arguments, nil)
|
|
267
491
|
|
|
268
492
|
# Optimize for the case that field is selected only once
|
|
269
493
|
if field_ast_nodes.nil? || field_ast_nodes.size == 1
|
|
270
494
|
next_selections = ast_node.selections
|
|
495
|
+
directives = ast_node.directives
|
|
271
496
|
else
|
|
272
497
|
next_selections = []
|
|
273
|
-
|
|
498
|
+
directives = []
|
|
499
|
+
field_ast_nodes.each { |f|
|
|
500
|
+
next_selections.concat(f.selections)
|
|
501
|
+
directives.concat(f.directives)
|
|
502
|
+
}
|
|
274
503
|
end
|
|
275
504
|
|
|
276
|
-
field_result = resolve_with_directives(object,
|
|
505
|
+
field_result = resolve_with_directives(object, directives) do
|
|
277
506
|
# Actually call the field resolver and capture the result
|
|
278
507
|
app_result = begin
|
|
279
508
|
query.with_error_handling do
|
|
@@ -284,10 +513,10 @@ module GraphQL
|
|
|
284
513
|
rescue GraphQL::ExecutionError => err
|
|
285
514
|
err
|
|
286
515
|
end
|
|
287
|
-
after_lazy(app_result, owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, scoped_context: context.scoped_context, owner_object: object, arguments:
|
|
288
|
-
continue_value = continue_value(next_path, inner_result, owner_type, field_defn, return_type.non_null?, ast_node)
|
|
516
|
+
after_lazy(app_result, owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, scoped_context: context.scoped_context, owner_object: object, arguments: resolved_arguments, result_name: result_name, result: selection_result) do |inner_result|
|
|
517
|
+
continue_value = continue_value(next_path, inner_result, owner_type, field_defn, return_type.non_null?, ast_node, result_name, selection_result)
|
|
289
518
|
if HALT != continue_value
|
|
290
|
-
continue_field(next_path, continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object,
|
|
519
|
+
continue_field(next_path, continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, resolved_arguments, result_name, selection_result)
|
|
291
520
|
end
|
|
292
521
|
end
|
|
293
522
|
end
|
|
@@ -304,43 +533,135 @@ module GraphQL
|
|
|
304
533
|
end
|
|
305
534
|
end
|
|
306
535
|
|
|
536
|
+
def dead_result?(selection_result)
|
|
537
|
+
selection_result.graphql_dead || ((parent = selection_result.graphql_parent) && parent.graphql_dead)
|
|
538
|
+
end
|
|
539
|
+
|
|
540
|
+
def set_result(selection_result, result_name, value)
|
|
541
|
+
if !dead_result?(selection_result)
|
|
542
|
+
if value.nil? &&
|
|
543
|
+
( # there are two conditions under which `nil` is not allowed in the response:
|
|
544
|
+
(selection_result.graphql_non_null_list_items) || # this value would be written into a list that doesn't allow nils
|
|
545
|
+
((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
|
|
546
|
+
)
|
|
547
|
+
# This is an invalid nil that should be propagated
|
|
548
|
+
# One caller of this method passes a block,
|
|
549
|
+
# namely when application code returns a `nil` to GraphQL and it doesn't belong there.
|
|
550
|
+
# The other possibility for reaching here is when a field returns an ExecutionError, so we write
|
|
551
|
+
# `nil` to the response, not knowing whether it's an invalid `nil` or not.
|
|
552
|
+
# (And in that case, we don't have to call the schema's handler, since it's not a bug in the application.)
|
|
553
|
+
# TODO the code is trying to tell me something.
|
|
554
|
+
yield if block_given?
|
|
555
|
+
parent = selection_result.graphql_parent
|
|
556
|
+
name_in_parent = selection_result.graphql_result_name
|
|
557
|
+
if parent.nil? # This is a top-level result hash
|
|
558
|
+
@response = nil
|
|
559
|
+
else
|
|
560
|
+
set_result(parent, name_in_parent, nil)
|
|
561
|
+
set_graphql_dead(selection_result)
|
|
562
|
+
end
|
|
563
|
+
else
|
|
564
|
+
selection_result[result_name] = value
|
|
565
|
+
end
|
|
566
|
+
end
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
# Mark this node and any already-registered children as dead,
|
|
570
|
+
# so that it accepts no more writes.
|
|
571
|
+
def set_graphql_dead(selection_result)
|
|
572
|
+
case selection_result
|
|
573
|
+
when GraphQLResultArray
|
|
574
|
+
selection_result.graphql_dead = true
|
|
575
|
+
selection_result.values.each { |v| set_graphql_dead(v) }
|
|
576
|
+
when GraphQLResultHash
|
|
577
|
+
selection_result.graphql_dead = true
|
|
578
|
+
selection_result.each { |k, v| set_graphql_dead(v) }
|
|
579
|
+
else
|
|
580
|
+
# It's a scalar, no way to mark it dead.
|
|
581
|
+
end
|
|
582
|
+
end
|
|
583
|
+
|
|
307
584
|
HALT = Object.new
|
|
308
|
-
def continue_value(path, value, parent_type, field, is_non_null, ast_node)
|
|
309
|
-
|
|
585
|
+
def continue_value(path, value, parent_type, field, is_non_null, ast_node, result_name, selection_result) # rubocop:disable Metrics/ParameterLists
|
|
586
|
+
case value
|
|
587
|
+
when nil
|
|
310
588
|
if is_non_null
|
|
311
|
-
|
|
312
|
-
|
|
589
|
+
set_result(selection_result, result_name, nil) do
|
|
590
|
+
# This block is called if `result_name` is not dead. (Maybe a previous invalid nil caused it be marked dead.)
|
|
591
|
+
err = parent_type::InvalidNullError.new(parent_type, field, value)
|
|
592
|
+
schema.type_error(err, context)
|
|
593
|
+
end
|
|
313
594
|
else
|
|
314
|
-
|
|
595
|
+
set_result(selection_result, result_name, nil)
|
|
315
596
|
end
|
|
316
597
|
HALT
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
598
|
+
when GraphQL::Error
|
|
599
|
+
# Handle these cases inside a single `when`
|
|
600
|
+
# to avoid the overhead of checking three different classes
|
|
601
|
+
# every time.
|
|
602
|
+
if value.is_a?(GraphQL::ExecutionError)
|
|
603
|
+
if selection_result.nil? || !dead_result?(selection_result)
|
|
604
|
+
value.path ||= path
|
|
605
|
+
value.ast_node ||= ast_node
|
|
606
|
+
context.errors << value
|
|
607
|
+
if selection_result
|
|
608
|
+
set_result(selection_result, result_name, nil)
|
|
609
|
+
end
|
|
610
|
+
end
|
|
611
|
+
HALT
|
|
612
|
+
elsif value.is_a?(GraphQL::UnauthorizedError)
|
|
613
|
+
# this hook might raise & crash, or it might return
|
|
614
|
+
# a replacement value
|
|
615
|
+
next_value = begin
|
|
616
|
+
schema.unauthorized_object(value)
|
|
617
|
+
rescue GraphQL::ExecutionError => err
|
|
618
|
+
err
|
|
619
|
+
end
|
|
620
|
+
continue_value(path, next_value, parent_type, field, is_non_null, ast_node, result_name, selection_result)
|
|
621
|
+
elsif GraphQL::Execution::Execute::SKIP == value
|
|
622
|
+
# It's possible a lazy was already written here
|
|
623
|
+
case selection_result
|
|
624
|
+
when GraphQLResultHash
|
|
625
|
+
selection_result.delete(result_name)
|
|
626
|
+
when GraphQLResultArray
|
|
627
|
+
selection_result.graphql_skip_at(result_name)
|
|
628
|
+
when nil
|
|
629
|
+
# this can happen with directives
|
|
630
|
+
else
|
|
631
|
+
raise "Invariant: unexpected result class #{selection_result.class} (#{selection_result.inspect})"
|
|
632
|
+
end
|
|
633
|
+
HALT
|
|
634
|
+
else
|
|
635
|
+
# What could this actually _be_? Anyhow,
|
|
636
|
+
# preserve the default behavior of doing nothing with it.
|
|
637
|
+
value
|
|
326
638
|
end
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
639
|
+
when Array
|
|
640
|
+
# It's an array full of execution errors; add them all.
|
|
641
|
+
if value.any? && value.all? { |v| v.is_a?(GraphQL::ExecutionError) }
|
|
642
|
+
list_type_at_all = (field && (field.type.list?))
|
|
643
|
+
if selection_result.nil? || !dead_result?(selection_result)
|
|
644
|
+
value.each_with_index do |error, index|
|
|
645
|
+
error.ast_node ||= ast_node
|
|
646
|
+
error.path ||= path + (list_type_at_all ? [index] : [])
|
|
647
|
+
context.errors << error
|
|
648
|
+
end
|
|
649
|
+
if selection_result
|
|
650
|
+
if list_type_at_all
|
|
651
|
+
result_without_errors = value.map { |v| v.is_a?(GraphQL::ExecutionError) ? nil : v }
|
|
652
|
+
set_result(selection_result, result_name, result_without_errors)
|
|
653
|
+
else
|
|
654
|
+
set_result(selection_result, result_name, nil)
|
|
655
|
+
end
|
|
656
|
+
end
|
|
657
|
+
end
|
|
658
|
+
HALT
|
|
659
|
+
else
|
|
660
|
+
value
|
|
336
661
|
end
|
|
337
|
-
|
|
338
|
-
continue_value(path, next_value, parent_type, field, is_non_null, ast_node)
|
|
339
|
-
elsif GraphQL::Execution::Execute::SKIP == value
|
|
340
|
-
HALT
|
|
341
|
-
elsif value.is_a?(GraphQL::Execution::Interpreter::RawValue)
|
|
662
|
+
when GraphQL::Execution::Interpreter::RawValue
|
|
342
663
|
# Write raw value directly to the response without resolving nested objects
|
|
343
|
-
|
|
664
|
+
set_result(selection_result, result_name, value.resolve)
|
|
344
665
|
HALT
|
|
345
666
|
else
|
|
346
667
|
value
|
|
@@ -355,17 +676,22 @@ module GraphQL
|
|
|
355
676
|
# Location information from `path` and `ast_node`.
|
|
356
677
|
#
|
|
357
678
|
# @return [Lazy, Array, Hash, Object] Lazy, Array, and Hash are all traversed to resolve lazy values later
|
|
358
|
-
def continue_field(path, value, owner_type, field, current_type, ast_node, next_selections, is_non_null, owner_object, arguments) # rubocop:disable Metrics/ParameterLists
|
|
679
|
+
def continue_field(path, value, owner_type, field, current_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result) # rubocop:disable Metrics/ParameterLists
|
|
680
|
+
if current_type.non_null?
|
|
681
|
+
current_type = current_type.of_type
|
|
682
|
+
is_non_null = true
|
|
683
|
+
end
|
|
684
|
+
|
|
359
685
|
case current_type.kind.name
|
|
360
686
|
when "SCALAR", "ENUM"
|
|
361
687
|
r = current_type.coerce_result(value, context)
|
|
362
|
-
|
|
688
|
+
set_result(selection_result, result_name, r)
|
|
363
689
|
r
|
|
364
690
|
when "UNION", "INTERFACE"
|
|
365
691
|
resolved_type_or_lazy, resolved_value = resolve_type(current_type, value, path)
|
|
366
692
|
resolved_value ||= value
|
|
367
693
|
|
|
368
|
-
after_lazy(resolved_type_or_lazy, owner: current_type, path: path, ast_node: ast_node, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments, trace: false) do |resolved_type|
|
|
694
|
+
after_lazy(resolved_type_or_lazy, owner: current_type, path: path, ast_node: ast_node, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result) do |resolved_type|
|
|
369
695
|
possible_types = query.possible_types(current_type)
|
|
370
696
|
|
|
371
697
|
if !possible_types.include?(resolved_type)
|
|
@@ -373,10 +699,10 @@ module GraphQL
|
|
|
373
699
|
err_class = current_type::UnresolvedTypeError
|
|
374
700
|
type_error = err_class.new(resolved_value, field, parent_type, resolved_type, possible_types)
|
|
375
701
|
schema.type_error(type_error, context)
|
|
376
|
-
|
|
702
|
+
set_result(selection_result, result_name, nil)
|
|
377
703
|
nil
|
|
378
704
|
else
|
|
379
|
-
continue_field(path, resolved_value, owner_type, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments)
|
|
705
|
+
continue_field(path, resolved_value, owner_type, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result)
|
|
380
706
|
end
|
|
381
707
|
end
|
|
382
708
|
when "OBJECT"
|
|
@@ -385,34 +711,67 @@ module GraphQL
|
|
|
385
711
|
rescue GraphQL::ExecutionError => err
|
|
386
712
|
err
|
|
387
713
|
end
|
|
388
|
-
after_lazy(object_proxy, owner: current_type, path: path, ast_node: ast_node, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments, trace: false) do |inner_object|
|
|
389
|
-
continue_value = continue_value(path, inner_object, owner_type, field, is_non_null, ast_node)
|
|
714
|
+
after_lazy(object_proxy, owner: current_type, path: path, ast_node: ast_node, scoped_context: context.scoped_context, field: field, owner_object: owner_object, arguments: arguments, trace: false, result_name: result_name, result: selection_result) do |inner_object|
|
|
715
|
+
continue_value = continue_value(path, inner_object, owner_type, field, is_non_null, ast_node, result_name, selection_result)
|
|
390
716
|
if HALT != continue_value
|
|
391
|
-
response_hash =
|
|
392
|
-
|
|
717
|
+
response_hash = GraphQLResultHash.new(result_name, selection_result)
|
|
718
|
+
set_result(selection_result, result_name, response_hash)
|
|
393
719
|
gathered_selections = gather_selections(continue_value, current_type, next_selections)
|
|
394
|
-
|
|
395
|
-
|
|
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|
|
|
729
|
+
if is_selection_array
|
|
730
|
+
this_result = GraphQLResultHash.new(result_name, selection_result)
|
|
731
|
+
final_result = response_hash
|
|
732
|
+
else
|
|
733
|
+
this_result = response_hash
|
|
734
|
+
final_result = nil
|
|
735
|
+
end
|
|
736
|
+
set_all_interpreter_context(continue_value, nil, nil, path) # reset this mutable state
|
|
737
|
+
resolve_with_directives(continue_value, selections.graphql_directives) do
|
|
738
|
+
evaluate_selections(
|
|
739
|
+
path,
|
|
740
|
+
context.scoped_context,
|
|
741
|
+
continue_value,
|
|
742
|
+
current_type,
|
|
743
|
+
false,
|
|
744
|
+
selections,
|
|
745
|
+
this_result,
|
|
746
|
+
final_result,
|
|
747
|
+
owner_object.object,
|
|
748
|
+
)
|
|
749
|
+
this_result
|
|
750
|
+
end
|
|
751
|
+
end
|
|
396
752
|
end
|
|
397
753
|
end
|
|
398
754
|
when "LIST"
|
|
399
|
-
response_list = []
|
|
400
|
-
write_in_response(path, response_list)
|
|
401
755
|
inner_type = current_type.of_type
|
|
756
|
+
response_list = GraphQLResultArray.new(result_name, selection_result)
|
|
757
|
+
response_list.graphql_non_null_list_items = inner_type.non_null?
|
|
758
|
+
set_result(selection_result, result_name, response_list)
|
|
759
|
+
|
|
402
760
|
idx = 0
|
|
403
761
|
scoped_context = context.scoped_context
|
|
404
762
|
begin
|
|
405
763
|
value.each do |inner_value|
|
|
764
|
+
break if dead_result?(response_list)
|
|
406
765
|
next_path = path.dup
|
|
407
766
|
next_path << idx
|
|
767
|
+
this_idx = idx
|
|
408
768
|
next_path.freeze
|
|
409
769
|
idx += 1
|
|
410
|
-
set_type_at_path(next_path, inner_type)
|
|
411
770
|
# This will update `response_list` with the lazy
|
|
412
|
-
after_lazy(inner_value, owner: inner_type, path: next_path, ast_node: ast_node, scoped_context: scoped_context, field: field, owner_object: owner_object, arguments: arguments) do |inner_inner_value|
|
|
413
|
-
continue_value = continue_value(next_path, inner_inner_value, owner_type, field, inner_type.non_null?, ast_node)
|
|
771
|
+
after_lazy(inner_value, owner: inner_type, path: next_path, ast_node: ast_node, scoped_context: scoped_context, field: field, owner_object: owner_object, arguments: arguments, result_name: this_idx, result: response_list) do |inner_inner_value|
|
|
772
|
+
continue_value = continue_value(next_path, inner_inner_value, owner_type, field, inner_type.non_null?, ast_node, this_idx, response_list)
|
|
414
773
|
if HALT != continue_value
|
|
415
|
-
continue_field(next_path, continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments)
|
|
774
|
+
continue_field(next_path, continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments, this_idx, response_list)
|
|
416
775
|
end
|
|
417
776
|
end
|
|
418
777
|
end
|
|
@@ -428,23 +787,18 @@ module GraphQL
|
|
|
428
787
|
end
|
|
429
788
|
|
|
430
789
|
response_list
|
|
431
|
-
when "NON_NULL"
|
|
432
|
-
inner_type = current_type.of_type
|
|
433
|
-
# Don't `set_type_at_path` because we want the static type,
|
|
434
|
-
# we're going to use that to determine whether a `nil` should be propagated or not.
|
|
435
|
-
continue_field(path, value, owner_type, field, inner_type, ast_node, next_selections, true, owner_object, arguments)
|
|
436
790
|
else
|
|
437
791
|
raise "Invariant: Unhandled type kind #{current_type.kind} (#{current_type})"
|
|
438
792
|
end
|
|
439
793
|
end
|
|
440
794
|
|
|
441
|
-
def resolve_with_directives(object,
|
|
442
|
-
return yield if
|
|
443
|
-
run_directive(object,
|
|
795
|
+
def resolve_with_directives(object, directives, &block)
|
|
796
|
+
return yield if directives.nil? || directives.empty?
|
|
797
|
+
run_directive(object, directives, 0, &block)
|
|
444
798
|
end
|
|
445
799
|
|
|
446
|
-
def run_directive(object,
|
|
447
|
-
dir_node =
|
|
800
|
+
def run_directive(object, directives, idx, &block)
|
|
801
|
+
dir_node = directives[idx]
|
|
448
802
|
if !dir_node
|
|
449
803
|
yield
|
|
450
804
|
else
|
|
@@ -452,9 +806,24 @@ module GraphQL
|
|
|
452
806
|
if !dir_defn.is_a?(Class)
|
|
453
807
|
dir_defn = dir_defn.type_class || raise("Only class-based directives are supported (not `@#{dir_node.name}`)")
|
|
454
808
|
end
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
809
|
+
raw_dir_args = arguments(nil, dir_defn, dir_node)
|
|
810
|
+
dir_args = continue_value(
|
|
811
|
+
@context[:current_path], # path
|
|
812
|
+
raw_dir_args, # value
|
|
813
|
+
dir_defn, # parent_type
|
|
814
|
+
nil, # field
|
|
815
|
+
false, # is_non_null
|
|
816
|
+
dir_node, # ast_node
|
|
817
|
+
nil, # result_name
|
|
818
|
+
nil, # selection_result
|
|
819
|
+
)
|
|
820
|
+
|
|
821
|
+
if dir_args == HALT
|
|
822
|
+
nil
|
|
823
|
+
else
|
|
824
|
+
dir_defn.resolve(object, dir_args, context) do
|
|
825
|
+
run_directive(object, directives, idx + 1, &block)
|
|
826
|
+
end
|
|
458
827
|
end
|
|
459
828
|
end
|
|
460
829
|
end
|
|
@@ -463,7 +832,7 @@ module GraphQL
|
|
|
463
832
|
def directives_include?(node, graphql_object, parent_type)
|
|
464
833
|
node.directives.each do |dir_node|
|
|
465
834
|
dir_defn = schema.directives.fetch(dir_node.name).type_class || raise("Only class-based directives are supported (not #{dir_node.name.inspect})")
|
|
466
|
-
args = arguments(graphql_object, dir_defn, dir_node)
|
|
835
|
+
args = arguments(graphql_object, dir_defn, dir_node)
|
|
467
836
|
if !dir_defn.include?(graphql_object, args, context)
|
|
468
837
|
return false
|
|
469
838
|
end
|
|
@@ -492,9 +861,8 @@ module GraphQL
|
|
|
492
861
|
# @param eager [Boolean] Set to `true` for mutation root fields only
|
|
493
862
|
# @param trace [Boolean] If `false`, don't wrap this with field tracing
|
|
494
863
|
# @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.
|
|
495
|
-
def after_lazy(lazy_obj, owner:, field:, path:, scoped_context:, owner_object:, arguments:, ast_node:, eager: false, trace: true, &block)
|
|
496
|
-
|
|
497
|
-
if schema.lazy?(lazy_obj)
|
|
864
|
+
def after_lazy(lazy_obj, owner:, field:, path:, scoped_context:, owner_object:, arguments:, ast_node:, result:, result_name:, eager: false, trace: true, &block)
|
|
865
|
+
if lazy?(lazy_obj)
|
|
498
866
|
lazy = GraphQL::Execution::Lazy.new(path: path, field: field) do
|
|
499
867
|
set_all_interpreter_context(owner_object, field, arguments, path)
|
|
500
868
|
context.scoped_context = scoped_context
|
|
@@ -502,27 +870,32 @@ module GraphQL
|
|
|
502
870
|
# but don't wrap the continuation below
|
|
503
871
|
inner_obj = begin
|
|
504
872
|
query.with_error_handling do
|
|
505
|
-
|
|
506
|
-
|
|
873
|
+
begin
|
|
874
|
+
if trace
|
|
875
|
+
query.trace("execute_field_lazy", {owner: owner, field: field, path: path, query: query, object: owner_object, arguments: arguments, ast_node: ast_node}) do
|
|
876
|
+
schema.sync_lazy(lazy_obj)
|
|
877
|
+
end
|
|
878
|
+
else
|
|
507
879
|
schema.sync_lazy(lazy_obj)
|
|
508
880
|
end
|
|
509
|
-
|
|
510
|
-
|
|
881
|
+
rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => err
|
|
882
|
+
err
|
|
511
883
|
end
|
|
512
884
|
end
|
|
513
|
-
|
|
514
|
-
|
|
885
|
+
rescue GraphQL::ExecutionError => ex_err
|
|
886
|
+
ex_err
|
|
515
887
|
end
|
|
516
|
-
|
|
888
|
+
yield(inner_obj)
|
|
517
889
|
end
|
|
518
890
|
|
|
519
891
|
if eager
|
|
520
892
|
lazy.value
|
|
521
893
|
else
|
|
522
|
-
|
|
894
|
+
set_result(result, result_name, lazy)
|
|
523
895
|
lazy
|
|
524
896
|
end
|
|
525
897
|
else
|
|
898
|
+
set_all_interpreter_context(owner_object, field, arguments, path)
|
|
526
899
|
yield(lazy_obj)
|
|
527
900
|
end
|
|
528
901
|
end
|
|
@@ -536,85 +909,6 @@ module GraphQL
|
|
|
536
909
|
end
|
|
537
910
|
end
|
|
538
911
|
|
|
539
|
-
def write_invalid_null_in_response(path, invalid_null_error)
|
|
540
|
-
if !dead_path?(path)
|
|
541
|
-
schema.type_error(invalid_null_error, context)
|
|
542
|
-
write_in_response(path, nil)
|
|
543
|
-
add_dead_path(path)
|
|
544
|
-
end
|
|
545
|
-
end
|
|
546
|
-
|
|
547
|
-
def write_execution_errors_in_response(path, errors)
|
|
548
|
-
if !dead_path?(path)
|
|
549
|
-
errors.each do |v|
|
|
550
|
-
context.errors << v
|
|
551
|
-
end
|
|
552
|
-
write_in_response(path, nil)
|
|
553
|
-
add_dead_path(path)
|
|
554
|
-
end
|
|
555
|
-
end
|
|
556
|
-
|
|
557
|
-
def write_in_response(path, value)
|
|
558
|
-
if dead_path?(path)
|
|
559
|
-
return
|
|
560
|
-
else
|
|
561
|
-
if value.nil? && path.any? && type_at(path).non_null?
|
|
562
|
-
# This nil is invalid, try writing it at the previous spot
|
|
563
|
-
propagate_path = path[0..-2]
|
|
564
|
-
write_in_response(propagate_path, value)
|
|
565
|
-
add_dead_path(propagate_path)
|
|
566
|
-
else
|
|
567
|
-
@response.write(path, value)
|
|
568
|
-
end
|
|
569
|
-
end
|
|
570
|
-
end
|
|
571
|
-
|
|
572
|
-
def value_at(path)
|
|
573
|
-
i = 0
|
|
574
|
-
value = @response.final_value
|
|
575
|
-
while value && (part = path[i])
|
|
576
|
-
value = value[part]
|
|
577
|
-
i += 1
|
|
578
|
-
end
|
|
579
|
-
value
|
|
580
|
-
end
|
|
581
|
-
|
|
582
|
-
# To propagate nulls, we have to know what the field type was
|
|
583
|
-
# at previous parts of the response.
|
|
584
|
-
# This hash matches the response
|
|
585
|
-
def type_at(path)
|
|
586
|
-
@types_at_paths.fetch(path)
|
|
587
|
-
end
|
|
588
|
-
|
|
589
|
-
def set_type_at_path(path, type)
|
|
590
|
-
@types_at_paths[path] = type
|
|
591
|
-
nil
|
|
592
|
-
end
|
|
593
|
-
|
|
594
|
-
# Mark `path` as having been permanently nulled out.
|
|
595
|
-
# No values will be added beyond that path.
|
|
596
|
-
def add_dead_path(path)
|
|
597
|
-
dead = @dead_paths
|
|
598
|
-
path.each do |part|
|
|
599
|
-
dead = dead[part] ||= {}
|
|
600
|
-
end
|
|
601
|
-
dead[:__dead] = true
|
|
602
|
-
end
|
|
603
|
-
|
|
604
|
-
def dead_path?(path)
|
|
605
|
-
res = @dead_paths
|
|
606
|
-
path.each do |part|
|
|
607
|
-
if res
|
|
608
|
-
if res[:__dead]
|
|
609
|
-
break
|
|
610
|
-
else
|
|
611
|
-
res = res[part]
|
|
612
|
-
end
|
|
613
|
-
end
|
|
614
|
-
end
|
|
615
|
-
res && res[:__dead]
|
|
616
|
-
end
|
|
617
|
-
|
|
618
912
|
# Set this pair in the Query context, but also in the interpeter namespace,
|
|
619
913
|
# for compatibility.
|
|
620
914
|
def set_interpreter_context(key, value)
|
|
@@ -633,7 +927,7 @@ module GraphQL
|
|
|
633
927
|
query.resolve_type(type, value)
|
|
634
928
|
end
|
|
635
929
|
|
|
636
|
-
if
|
|
930
|
+
if lazy?(resolved_type)
|
|
637
931
|
GraphQL::Execution::Lazy.new do
|
|
638
932
|
query.trace("resolve_type_lazy", trace_payload) do
|
|
639
933
|
schema.sync_lazy(resolved_type)
|
|
@@ -647,6 +941,12 @@ module GraphQL
|
|
|
647
941
|
def authorized_new(type, value, context)
|
|
648
942
|
type.authorized_new(value, context)
|
|
649
943
|
end
|
|
944
|
+
|
|
945
|
+
def lazy?(object)
|
|
946
|
+
@lazy_cache.fetch(object.class) {
|
|
947
|
+
@lazy_cache[object.class] = @schema.lazy?(object)
|
|
948
|
+
}
|
|
949
|
+
end
|
|
650
950
|
end
|
|
651
951
|
end
|
|
652
952
|
end
|