graphql 1.11.3 → 1.12.0
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/core.rb +8 -0
- data/lib/generators/graphql/install_generator.rb +5 -5
- data/lib/generators/graphql/object_generator.rb +2 -0
- data/lib/generators/graphql/relay_generator.rb +63 -0
- data/lib/generators/graphql/templates/base_argument.erb +2 -0
- data/lib/generators/graphql/templates/base_connection.erb +8 -0
- data/lib/generators/graphql/templates/base_edge.erb +8 -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_mutation.erb +2 -0
- data/lib/generators/graphql/templates/base_object.erb +2 -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/enum.erb +2 -0
- data/lib/generators/graphql/templates/graphql_controller.erb +2 -0
- data/lib/generators/graphql/templates/interface.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/mutation_type.erb +2 -0
- data/lib/generators/graphql/templates/node_type.erb +9 -0
- data/lib/generators/graphql/templates/object.erb +3 -1
- data/lib/generators/graphql/templates/query_type.erb +3 -3
- data/lib/generators/graphql/templates/scalar.erb +2 -0
- data/lib/generators/graphql/templates/schema.erb +10 -35
- data/lib/generators/graphql/templates/union.erb +3 -1
- data/lib/graphql.rb +55 -4
- data/lib/graphql/analysis/analyze_query.rb +7 -0
- data/lib/graphql/analysis/ast.rb +11 -2
- data/lib/graphql/analysis/ast/visitor.rb +9 -1
- data/lib/graphql/argument.rb +3 -3
- data/lib/graphql/backtrace.rb +28 -19
- data/lib/graphql/backtrace/legacy_tracer.rb +56 -0
- data/lib/graphql/backtrace/table.rb +22 -2
- data/lib/graphql/backtrace/tracer.rb +40 -8
- data/lib/graphql/backwards_compatibility.rb +1 -0
- data/lib/graphql/compatibility/execution_specification.rb +1 -0
- data/lib/graphql/compatibility/lazy_execution_specification.rb +2 -0
- data/lib/graphql/compatibility/query_parser_specification.rb +2 -0
- data/lib/graphql/compatibility/schema_parser_specification.rb +2 -0
- data/lib/graphql/dataloader.rb +197 -0
- data/lib/graphql/dataloader/null_dataloader.rb +21 -0
- data/lib/graphql/dataloader/request.rb +24 -0
- data/lib/graphql/dataloader/request_all.rb +22 -0
- data/lib/graphql/dataloader/source.rb +93 -0
- data/lib/graphql/define/assign_global_id_field.rb +2 -2
- data/lib/graphql/define/instance_definable.rb +32 -2
- data/lib/graphql/define/type_definer.rb +5 -5
- data/lib/graphql/deprecated_dsl.rb +5 -0
- data/lib/graphql/enum_type.rb +2 -0
- data/lib/graphql/execution/errors.rb +4 -0
- data/lib/graphql/execution/execute.rb +7 -0
- data/lib/graphql/execution/interpreter.rb +20 -6
- data/lib/graphql/execution/interpreter/arguments.rb +57 -5
- data/lib/graphql/execution/interpreter/arguments_cache.rb +8 -0
- data/lib/graphql/execution/interpreter/handles_raw_value.rb +0 -7
- data/lib/graphql/execution/interpreter/runtime.rb +251 -138
- data/lib/graphql/execution/multiplex.rb +20 -6
- data/lib/graphql/function.rb +4 -0
- data/lib/graphql/input_object_type.rb +2 -0
- data/lib/graphql/integer_decoding_error.rb +17 -0
- data/lib/graphql/interface_type.rb +3 -1
- data/lib/graphql/introspection.rb +96 -0
- data/lib/graphql/introspection/field_type.rb +7 -3
- data/lib/graphql/introspection/input_value_type.rb +6 -0
- data/lib/graphql/introspection/introspection_query.rb +6 -92
- data/lib/graphql/introspection/type_type.rb +7 -3
- data/lib/graphql/invalid_null_error.rb +1 -1
- data/lib/graphql/language/block_string.rb +24 -5
- data/lib/graphql/language/document_from_schema_definition.rb +50 -23
- data/lib/graphql/language/lexer.rb +7 -3
- data/lib/graphql/language/lexer.rl +7 -3
- data/lib/graphql/language/nodes.rb +1 -1
- data/lib/graphql/language/parser.rb +107 -103
- data/lib/graphql/language/parser.y +4 -0
- data/lib/graphql/language/sanitized_printer.rb +59 -26
- data/lib/graphql/name_validator.rb +6 -7
- data/lib/graphql/object_type.rb +2 -0
- data/lib/graphql/pagination/connection.rb +5 -1
- data/lib/graphql/pagination/connections.rb +15 -17
- data/lib/graphql/query.rb +8 -3
- data/lib/graphql/query/context.rb +38 -4
- data/lib/graphql/query/fingerprint.rb +2 -0
- data/lib/graphql/query/serial_execution.rb +1 -0
- data/lib/graphql/query/validation_pipeline.rb +4 -1
- data/lib/graphql/relay/array_connection.rb +2 -2
- data/lib/graphql/relay/base_connection.rb +7 -0
- data/lib/graphql/relay/connection_instrumentation.rb +4 -4
- data/lib/graphql/relay/connection_type.rb +1 -1
- data/lib/graphql/relay/mutation.rb +1 -0
- data/lib/graphql/relay/node.rb +3 -0
- data/lib/graphql/relay/range_add.rb +14 -5
- data/lib/graphql/relay/type_extensions.rb +2 -0
- data/lib/graphql/scalar_type.rb +2 -0
- data/lib/graphql/schema.rb +107 -38
- data/lib/graphql/schema/argument.rb +74 -5
- data/lib/graphql/schema/build_from_definition.rb +203 -86
- data/lib/graphql/schema/default_type_error.rb +2 -0
- data/lib/graphql/schema/directive.rb +76 -0
- data/lib/graphql/schema/directive/deprecated.rb +1 -1
- data/lib/graphql/schema/directive/flagged.rb +57 -0
- data/lib/graphql/schema/enum.rb +3 -0
- data/lib/graphql/schema/enum_value.rb +12 -6
- data/lib/graphql/schema/field.rb +59 -24
- data/lib/graphql/schema/field/connection_extension.rb +11 -9
- data/lib/graphql/schema/field/scope_extension.rb +1 -1
- data/lib/graphql/schema/input_object.rb +38 -25
- data/lib/graphql/schema/interface.rb +2 -1
- data/lib/graphql/schema/late_bound_type.rb +2 -2
- data/lib/graphql/schema/loader.rb +1 -0
- data/lib/graphql/schema/member.rb +4 -0
- data/lib/graphql/schema/member/base_dsl_methods.rb +1 -0
- data/lib/graphql/schema/member/build_type.rb +17 -7
- data/lib/graphql/schema/member/has_arguments.rb +70 -51
- data/lib/graphql/schema/member/has_deprecation_reason.rb +25 -0
- data/lib/graphql/schema/member/has_directives.rb +98 -0
- data/lib/graphql/schema/member/has_fields.rb +2 -2
- data/lib/graphql/schema/member/has_validators.rb +31 -0
- data/lib/graphql/schema/member/type_system_helpers.rb +3 -3
- data/lib/graphql/schema/object.rb +11 -0
- data/lib/graphql/schema/printer.rb +5 -4
- data/lib/graphql/schema/relay_classic_mutation.rb +4 -2
- data/lib/graphql/schema/resolver.rb +7 -0
- data/lib/graphql/schema/resolver/has_payload_type.rb +2 -0
- data/lib/graphql/schema/subscription.rb +20 -12
- data/lib/graphql/schema/timeout.rb +29 -15
- data/lib/graphql/schema/timeout_middleware.rb +2 -0
- data/lib/graphql/schema/unique_within_type.rb +1 -2
- data/lib/graphql/schema/validation.rb +10 -0
- data/lib/graphql/schema/validator.rb +163 -0
- data/lib/graphql/schema/validator/exclusion_validator.rb +31 -0
- data/lib/graphql/schema/validator/format_validator.rb +49 -0
- data/lib/graphql/schema/validator/inclusion_validator.rb +33 -0
- data/lib/graphql/schema/validator/length_validator.rb +57 -0
- data/lib/graphql/schema/validator/numericality_validator.rb +71 -0
- data/lib/graphql/schema/validator/required_validator.rb +68 -0
- data/lib/graphql/schema/warden.rb +2 -3
- data/lib/graphql/static_validation.rb +1 -0
- data/lib/graphql/static_validation/all_rules.rb +1 -0
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +25 -17
- data/lib/graphql/static_validation/rules/input_object_names_are_unique.rb +30 -0
- data/lib/graphql/static_validation/rules/input_object_names_are_unique_error.rb +30 -0
- data/lib/graphql/static_validation/validation_timeout_error.rb +25 -0
- data/lib/graphql/static_validation/validator.rb +31 -7
- data/lib/graphql/subscriptions.rb +23 -16
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +21 -7
- data/lib/graphql/tracing.rb +2 -2
- data/lib/graphql/tracing/appoptics_tracing.rb +12 -2
- data/lib/graphql/tracing/platform_tracing.rb +4 -2
- data/lib/graphql/tracing/prometheus_tracing/graphql_collector.rb +4 -1
- data/lib/graphql/tracing/skylight_tracing.rb +1 -1
- data/lib/graphql/types/int.rb +9 -2
- data/lib/graphql/types/iso_8601_date_time.rb +2 -1
- data/lib/graphql/types/relay.rb +11 -3
- data/lib/graphql/types/relay/base_connection.rb +2 -90
- data/lib/graphql/types/relay/base_edge.rb +2 -34
- data/lib/graphql/types/relay/connection_behaviors.rb +123 -0
- data/lib/graphql/types/relay/default_relay.rb +27 -0
- data/lib/graphql/types/relay/edge_behaviors.rb +42 -0
- data/lib/graphql/types/relay/has_node_field.rb +41 -0
- data/lib/graphql/types/relay/has_nodes_field.rb +41 -0
- data/lib/graphql/types/relay/node.rb +2 -4
- data/lib/graphql/types/relay/node_behaviors.rb +15 -0
- data/lib/graphql/types/relay/node_field.rb +1 -19
- data/lib/graphql/types/relay/nodes_field.rb +1 -19
- data/lib/graphql/types/relay/page_info.rb +2 -14
- data/lib/graphql/types/relay/page_info_behaviors.rb +25 -0
- data/lib/graphql/types/string.rb +7 -1
- data/lib/graphql/unauthorized_error.rb +1 -1
- data/lib/graphql/union_type.rb +2 -0
- data/lib/graphql/upgrader/member.rb +1 -0
- data/lib/graphql/upgrader/schema.rb +1 -0
- data/lib/graphql/version.rb +1 -1
- data/readme.md +1 -1
- metadata +38 -9
- data/lib/graphql/types/relay/base_field.rb +0 -22
- data/lib/graphql/types/relay/base_interface.rb +0 -29
- data/lib/graphql/types/relay/base_object.rb +0 -26
@@ -29,11 +29,16 @@ module GraphQL
|
|
29
29
|
|
30
30
|
private
|
31
31
|
|
32
|
+
NO_ARGUMENTS = {}.freeze
|
33
|
+
|
32
34
|
NO_VALUE_GIVEN = Object.new
|
33
35
|
|
34
36
|
def prepare_args_hash(ast_arg_or_hash_or_value)
|
35
37
|
case ast_arg_or_hash_or_value
|
36
38
|
when Hash
|
39
|
+
if ast_arg_or_hash_or_value.empty?
|
40
|
+
return NO_ARGUMENTS
|
41
|
+
end
|
37
42
|
args_hash = {}
|
38
43
|
ast_arg_or_hash_or_value.each do |k, v|
|
39
44
|
args_hash[k] = prepare_args_hash(v)
|
@@ -42,6 +47,9 @@ module GraphQL
|
|
42
47
|
when Array
|
43
48
|
ast_arg_or_hash_or_value.map { |v| prepare_args_hash(v) }
|
44
49
|
when GraphQL::Language::Nodes::Field, GraphQL::Language::Nodes::InputObject, GraphQL::Language::Nodes::Directive
|
50
|
+
if ast_arg_or_hash_or_value.arguments.empty?
|
51
|
+
return NO_ARGUMENTS
|
52
|
+
end
|
45
53
|
args_hash = {}
|
46
54
|
ast_arg_or_hash_or_value.arguments.each do |arg|
|
47
55
|
v = prepare_args_hash(arg.value)
|
@@ -19,8 +19,10 @@ module GraphQL
|
|
19
19
|
|
20
20
|
def initialize(query:, response:)
|
21
21
|
@query = query
|
22
|
+
@dataloader = query.multiplex.dataloader
|
22
23
|
@schema = query.schema
|
23
24
|
@context = query.context
|
25
|
+
@multiplex_context = query.multiplex.context
|
24
26
|
@interpreter_context = @context.namespace(:interpreter)
|
25
27
|
@response = response
|
26
28
|
@dead_paths = {}
|
@@ -43,26 +45,63 @@ module GraphQL
|
|
43
45
|
# might be stored up in lazies.
|
44
46
|
# @return [void]
|
45
47
|
def run_eager
|
46
|
-
|
47
48
|
root_operation = query.selected_operation
|
48
49
|
root_op_type = root_operation.operation_type || "query"
|
49
50
|
root_type = schema.root_type_for_operation(root_op_type)
|
50
51
|
path = []
|
51
|
-
|
52
|
-
@interpreter_context[:current_path] = path
|
52
|
+
set_all_interpreter_context(query.root_value, nil, nil, path)
|
53
53
|
object_proxy = authorized_new(root_type, query.root_value, context, path)
|
54
54
|
object_proxy = schema.sync_lazy(object_proxy)
|
55
55
|
if object_proxy.nil?
|
56
56
|
# Root .authorized? returned false.
|
57
57
|
write_in_response(path, nil)
|
58
|
-
nil
|
59
58
|
else
|
60
|
-
|
61
|
-
|
59
|
+
# Prepare this runtime state to be encapsulated in a Fiber
|
60
|
+
@progress_path = path
|
61
|
+
@progress_scoped_context = context.scoped_context
|
62
|
+
@progress_object = object_proxy
|
63
|
+
@progress_object_type = root_type
|
64
|
+
@progress_index = nil
|
65
|
+
@progress_is_eager_selection = root_op_type == "mutation"
|
66
|
+
@progress_selections = gather_selections(object_proxy, root_type, root_operation.selections)
|
67
|
+
|
68
|
+
# Make the first fiber which will begin execution
|
69
|
+
enqueue_selections_fiber
|
62
70
|
end
|
71
|
+
delete_interpreter_context(:current_path)
|
72
|
+
delete_interpreter_context(:current_field)
|
73
|
+
delete_interpreter_context(:current_object)
|
74
|
+
delete_interpreter_context(:current_arguments)
|
75
|
+
nil
|
76
|
+
end
|
77
|
+
|
78
|
+
# Use `@dataloader` to enqueue a fiber that will pick up from the current point.
|
79
|
+
# @return [void]
|
80
|
+
def enqueue_selections_fiber
|
81
|
+
# Read these into local variables so that later assignments don't affect the block below.
|
82
|
+
path = @progress_path
|
83
|
+
scoped_context = @progress_scoped_context
|
84
|
+
owner_object = @progress_object
|
85
|
+
owner_type = @progress_object_type
|
86
|
+
idx = @progress_index
|
87
|
+
is_eager_selection = @progress_is_eager_selection
|
88
|
+
gathered_selections = @progress_selections
|
89
|
+
|
90
|
+
@dataloader.enqueue {
|
91
|
+
evaluate_selections(
|
92
|
+
path,
|
93
|
+
scoped_context,
|
94
|
+
owner_object,
|
95
|
+
owner_type,
|
96
|
+
is_eager_selection: is_eager_selection,
|
97
|
+
after: idx,
|
98
|
+
gathered_selections: gathered_selections,
|
99
|
+
)
|
100
|
+
}
|
101
|
+
nil
|
63
102
|
end
|
64
103
|
|
65
|
-
def gather_selections(owner_object, owner_type, selections, selections_by_name)
|
104
|
+
def gather_selections(owner_object, owner_type, selections, selections_by_name = {})
|
66
105
|
selections.each do |node|
|
67
106
|
# Skip gathering this if the directive says so
|
68
107
|
if !directives_include?(node, owner_object, owner_type)
|
@@ -114,147 +153,191 @@ module GraphQL
|
|
114
153
|
raise "Invariant: unexpected selection class: #{node.class}"
|
115
154
|
end
|
116
155
|
end
|
156
|
+
selections_by_name
|
117
157
|
end
|
118
158
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
159
|
+
NO_ARGS = {}.freeze
|
160
|
+
|
161
|
+
# @return [void]
|
162
|
+
def evaluate_selections(path, scoped_context, owner_object, owner_type, is_eager_selection:, gathered_selections:, after:)
|
163
|
+
set_all_interpreter_context(owner_object, nil, nil, path)
|
164
|
+
|
165
|
+
@progress_path = path
|
166
|
+
@progress_scoped_context = scoped_context
|
167
|
+
@progress_object = owner_object
|
168
|
+
@progress_object_type = owner_type
|
169
|
+
@progress_index = nil
|
170
|
+
@progress_is_eager_selection = is_eager_selection
|
171
|
+
@progress_selections = gathered_selections
|
172
|
+
|
173
|
+
# Track `idx` manually to avoid an allocation on this hot path
|
174
|
+
idx = 0
|
175
|
+
gathered_selections.each do |result_name, field_ast_nodes_or_ast_node|
|
176
|
+
prev_idx = idx
|
177
|
+
idx += 1
|
178
|
+
# TODO: this is how a `progress` resumes where this left off.
|
179
|
+
# Is there a better way to seek in the hash?
|
180
|
+
# I think we could also use the array of keys; it supports seeking just fine.
|
181
|
+
if after && prev_idx <= after
|
182
|
+
next
|
134
183
|
end
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
dynamic_field
|
145
|
-
else
|
146
|
-
raise "Invariant: no field for #{owner_type}.#{field_name}"
|
147
|
-
end
|
184
|
+
@progress_index = prev_idx
|
185
|
+
# This is how the current runtime gives itself to `dataloader`
|
186
|
+
# so that the dataloader can enqueue another fiber to resume if needed.
|
187
|
+
@dataloader.current_runtime = self
|
188
|
+
evaluate_selection(path, result_name, field_ast_nodes_or_ast_node, scoped_context, owner_object, owner_type, is_eager_selection)
|
189
|
+
# The dataloader knows if ^^ that selection halted and later selections were executed in another fiber.
|
190
|
+
# If that's the case, then don't continue execution here.
|
191
|
+
if @dataloader.yielded?
|
192
|
+
break
|
148
193
|
end
|
149
|
-
|
194
|
+
end
|
195
|
+
nil
|
196
|
+
end
|
150
197
|
|
151
|
-
|
152
|
-
|
153
|
-
|
198
|
+
# @return [void]
|
199
|
+
def evaluate_selection(path, result_name, field_ast_nodes_or_ast_node, scoped_context, owner_object, owner_type, is_eager_field)
|
200
|
+
# As a performance optimization, the hash key will be a `Node` if
|
201
|
+
# there's only one selection of the field. But if there are multiple
|
202
|
+
# selections of the field, it will be an Array of nodes
|
203
|
+
if field_ast_nodes_or_ast_node.is_a?(Array)
|
204
|
+
field_ast_nodes = field_ast_nodes_or_ast_node
|
205
|
+
ast_node = field_ast_nodes.first
|
206
|
+
else
|
207
|
+
field_ast_nodes = nil
|
208
|
+
ast_node = field_ast_nodes_or_ast_node
|
209
|
+
end
|
210
|
+
field_name = ast_node.name
|
211
|
+
field_defn = @fields_cache[owner_type][field_name] ||= owner_type.get_field(field_name)
|
212
|
+
is_introspection = false
|
213
|
+
if field_defn.nil?
|
214
|
+
field_defn = if owner_type == schema.query && (entry_point_field = schema.introspection_system.entry_point(name: field_name))
|
215
|
+
is_introspection = true
|
216
|
+
entry_point_field
|
217
|
+
elsif (dynamic_field = schema.introspection_system.dynamic_field(name: field_name))
|
218
|
+
is_introspection = true
|
219
|
+
dynamic_field
|
220
|
+
else
|
221
|
+
raise "Invariant: no field for #{owner_type}.#{field_name}"
|
222
|
+
end
|
223
|
+
end
|
224
|
+
return_type = field_defn.type
|
154
225
|
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
set_type_at_path(next_path, return_type)
|
159
|
-
# Set this before calling `run_with_directives`, so that the directive can have the latest path
|
160
|
-
@interpreter_context[:current_path] = next_path
|
161
|
-
@interpreter_context[:current_field] = field_defn
|
226
|
+
next_path = path.dup
|
227
|
+
next_path << result_name
|
228
|
+
next_path.freeze
|
162
229
|
|
163
|
-
|
164
|
-
|
230
|
+
# This seems janky, but we need to know
|
231
|
+
# the field's return type at this path in order
|
232
|
+
# to propagate `null`
|
233
|
+
set_type_at_path(next_path, return_type)
|
234
|
+
# Set this before calling `run_with_directives`, so that the directive can have the latest path
|
235
|
+
set_all_interpreter_context(nil, field_defn, nil, next_path)
|
165
236
|
|
166
|
-
|
167
|
-
|
168
|
-
end
|
237
|
+
context.scoped_context = scoped_context
|
238
|
+
object = owner_object
|
169
239
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
continue_value(next_path, e, field_defn, return_type.non_null?, ast_node)
|
174
|
-
next
|
175
|
-
end
|
240
|
+
if is_introspection
|
241
|
+
object = authorized_new(field_defn.owner, object, context, next_path)
|
242
|
+
end
|
176
243
|
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
244
|
+
begin
|
245
|
+
kwarg_arguments = arguments(object, field_defn, ast_node)
|
246
|
+
rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => e
|
247
|
+
continue_value(next_path, e, owner_type, field_defn, return_type.non_null?, ast_node)
|
248
|
+
return
|
249
|
+
end
|
182
250
|
|
183
|
-
|
251
|
+
after_lazy(kwarg_arguments, owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, scoped_context: context.scoped_context, owner_object: object, arguments: kwarg_arguments) do |resolved_arguments|
|
252
|
+
if resolved_arguments.is_a?(GraphQL::ExecutionError) || resolved_arguments.is_a?(GraphQL::UnauthorizedError)
|
253
|
+
continue_value(next_path, resolved_arguments, owner_type, field_defn, return_type.non_null?, ast_node)
|
254
|
+
next
|
255
|
+
end
|
184
256
|
|
257
|
+
kwarg_arguments = if resolved_arguments.empty? && field_defn.extras.empty?
|
258
|
+
# We can avoid allocating the `{ Symbol => Object }` hash in this case
|
259
|
+
NO_ARGS
|
260
|
+
else
|
261
|
+
# Bundle up the extras, then make a new arguments instance
|
262
|
+
# that includes the extras, too.
|
263
|
+
extra_args = {}
|
185
264
|
field_defn.extras.each do |extra|
|
186
265
|
case extra
|
187
266
|
when :ast_node
|
188
|
-
|
267
|
+
extra_args[:ast_node] = ast_node
|
189
268
|
when :execution_errors
|
190
|
-
|
269
|
+
extra_args[:execution_errors] = ExecutionErrors.new(context, ast_node, next_path)
|
191
270
|
when :path
|
192
|
-
|
271
|
+
extra_args[:path] = next_path
|
193
272
|
when :lookahead
|
194
273
|
if !field_ast_nodes
|
195
274
|
field_ast_nodes = [ast_node]
|
196
275
|
end
|
197
|
-
|
276
|
+
|
277
|
+
extra_args[:lookahead] = Execution::Lookahead.new(
|
198
278
|
query: query,
|
199
279
|
ast_nodes: field_ast_nodes,
|
200
280
|
field: field_defn,
|
201
281
|
)
|
202
282
|
when :argument_details
|
203
|
-
|
283
|
+
# Use this flag to tell Interpreter::Arguments to add itself
|
284
|
+
# to the keyword args hash _before_ freezing everything.
|
285
|
+
extra_args[:argument_details] = :__arguments_add_self
|
204
286
|
else
|
205
|
-
|
287
|
+
extra_args[extra] = field_defn.fetch_extra(extra, context)
|
206
288
|
end
|
207
289
|
end
|
290
|
+
resolved_arguments = resolved_arguments.merge_extras(extra_args)
|
291
|
+
resolved_arguments.keyword_arguments
|
292
|
+
end
|
208
293
|
|
209
|
-
|
294
|
+
set_all_interpreter_context(nil, nil, kwarg_arguments, nil)
|
210
295
|
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
296
|
+
# Optimize for the case that field is selected only once
|
297
|
+
if field_ast_nodes.nil? || field_ast_nodes.size == 1
|
298
|
+
next_selections = ast_node.selections
|
299
|
+
else
|
300
|
+
next_selections = []
|
301
|
+
field_ast_nodes.each { |f| next_selections.concat(f.selections) }
|
302
|
+
end
|
218
303
|
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
end
|
304
|
+
field_result = resolve_with_directives(object, ast_node) do
|
305
|
+
# Actually call the field resolver and capture the result
|
306
|
+
app_result = begin
|
307
|
+
query.with_error_handling do
|
308
|
+
query.trace("execute_field", {owner: owner_type, field: field_defn, path: next_path, ast_node: ast_node, query: query, object: object, arguments: kwarg_arguments}) do
|
309
|
+
field_defn.resolve(object, kwarg_arguments, context)
|
226
310
|
end
|
227
|
-
rescue GraphQL::ExecutionError => err
|
228
|
-
err
|
229
311
|
end
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
312
|
+
rescue GraphQL::ExecutionError => err
|
313
|
+
err
|
314
|
+
end
|
315
|
+
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: kwarg_arguments) do |inner_result|
|
316
|
+
continue_value = continue_value(next_path, inner_result, owner_type, field_defn, return_type.non_null?, ast_node)
|
317
|
+
if RawValue === continue_value
|
318
|
+
# Write raw value directly to the response without resolving nested objects
|
319
|
+
write_in_response(next_path, continue_value.resolve)
|
320
|
+
elsif HALT != continue_value
|
321
|
+
continue_field(next_path, continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, kwarg_arguments)
|
238
322
|
end
|
239
323
|
end
|
324
|
+
end
|
240
325
|
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
else
|
247
|
-
field_result
|
248
|
-
end
|
326
|
+
# If this field is a root mutation field, immediately resolve
|
327
|
+
# all of its child fields before moving on to the next root mutation field.
|
328
|
+
# (Subselections of this mutation will still be resolved level-by-level.)
|
329
|
+
if is_eager_field
|
330
|
+
Interpreter::Resolve.resolve_all([field_result])
|
249
331
|
end
|
332
|
+
|
333
|
+
nil
|
250
334
|
end
|
251
335
|
end
|
252
336
|
|
253
337
|
HALT = Object.new
|
254
|
-
def continue_value(path, value, field, is_non_null, ast_node)
|
338
|
+
def continue_value(path, value, parent_type, field, is_non_null, ast_node)
|
255
339
|
if value.nil?
|
256
340
|
if is_non_null
|
257
|
-
parent_type = field.owner_type
|
258
341
|
err = parent_type::InvalidNullError.new(parent_type, field, value)
|
259
342
|
write_invalid_null_in_response(path, err)
|
260
343
|
else
|
@@ -282,7 +365,7 @@ module GraphQL
|
|
282
365
|
err
|
283
366
|
end
|
284
367
|
|
285
|
-
continue_value(path, next_value, field, is_non_null, ast_node)
|
368
|
+
continue_value(path, next_value, parent_type, field, is_non_null, ast_node)
|
286
369
|
elsif GraphQL::Execution::Execute::SKIP == value
|
287
370
|
HALT
|
288
371
|
else
|
@@ -298,49 +381,50 @@ module GraphQL
|
|
298
381
|
# Location information from `path` and `ast_node`.
|
299
382
|
#
|
300
383
|
# @return [Lazy, Array, Hash, Object] Lazy, Array, and Hash are all traversed to resolve lazy values later
|
301
|
-
def continue_field(path, value, field,
|
302
|
-
case
|
384
|
+
def continue_field(path, value, owner_type, field, current_type, ast_node, next_selections, is_non_null, owner_object, arguments) # rubocop:disable Metrics/ParameterLists
|
385
|
+
case current_type.kind.name
|
303
386
|
when "SCALAR", "ENUM"
|
304
|
-
r =
|
387
|
+
r = current_type.coerce_result(value, context)
|
305
388
|
write_in_response(path, r)
|
306
389
|
r
|
307
390
|
when "UNION", "INTERFACE"
|
308
|
-
resolved_type_or_lazy, resolved_value = resolve_type(
|
391
|
+
resolved_type_or_lazy, resolved_value = resolve_type(current_type, value, path)
|
309
392
|
resolved_value ||= value
|
310
393
|
|
311
|
-
after_lazy(resolved_type_or_lazy, owner:
|
312
|
-
possible_types = query.possible_types(
|
394
|
+
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|
|
395
|
+
possible_types = query.possible_types(current_type)
|
313
396
|
|
314
397
|
if !possible_types.include?(resolved_type)
|
315
398
|
parent_type = field.owner_type
|
316
|
-
err_class =
|
399
|
+
err_class = current_type::UnresolvedTypeError
|
317
400
|
type_error = err_class.new(resolved_value, field, parent_type, resolved_type, possible_types)
|
318
401
|
schema.type_error(type_error, context)
|
319
402
|
write_in_response(path, nil)
|
320
403
|
nil
|
321
404
|
else
|
322
|
-
continue_field(path, resolved_value, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments)
|
405
|
+
continue_field(path, resolved_value, owner_type, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments)
|
323
406
|
end
|
324
407
|
end
|
325
408
|
when "OBJECT"
|
326
409
|
object_proxy = begin
|
327
|
-
authorized_new(
|
410
|
+
authorized_new(current_type, value, context, path)
|
328
411
|
rescue GraphQL::ExecutionError => err
|
329
412
|
err
|
330
413
|
end
|
331
|
-
after_lazy(object_proxy, owner:
|
332
|
-
continue_value = continue_value(path, inner_object, field, is_non_null, ast_node)
|
414
|
+
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|
|
415
|
+
continue_value = continue_value(path, inner_object, owner_type, field, is_non_null, ast_node)
|
333
416
|
if HALT != continue_value
|
334
417
|
response_hash = {}
|
335
418
|
write_in_response(path, response_hash)
|
336
|
-
|
419
|
+
gathered_selections = gather_selections(continue_value, current_type, next_selections)
|
420
|
+
evaluate_selections(path, context.scoped_context, continue_value, current_type, is_eager_selection: false, gathered_selections: gathered_selections, after: nil)
|
337
421
|
response_hash
|
338
422
|
end
|
339
423
|
end
|
340
424
|
when "LIST"
|
341
425
|
response_list = []
|
342
426
|
write_in_response(path, response_list)
|
343
|
-
inner_type =
|
427
|
+
inner_type = current_type.of_type
|
344
428
|
idx = 0
|
345
429
|
scoped_context = context.scoped_context
|
346
430
|
begin
|
@@ -351,10 +435,10 @@ module GraphQL
|
|
351
435
|
idx += 1
|
352
436
|
set_type_at_path(next_path, inner_type)
|
353
437
|
# This will update `response_list` with the lazy
|
354
|
-
after_lazy(inner_value, owner: inner_type, path: next_path, scoped_context: scoped_context, field: field, owner_object: owner_object, arguments: arguments) do |inner_inner_value|
|
355
|
-
continue_value = continue_value(next_path, inner_inner_value, field, inner_type.non_null?, ast_node)
|
438
|
+
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|
|
439
|
+
continue_value = continue_value(next_path, inner_inner_value, owner_type, field, inner_type.non_null?, ast_node)
|
356
440
|
if HALT != continue_value
|
357
|
-
continue_field(next_path, continue_value, field, inner_type, ast_node, next_selections, false, owner_object, arguments)
|
441
|
+
continue_field(next_path, continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments)
|
358
442
|
end
|
359
443
|
end
|
360
444
|
end
|
@@ -371,12 +455,12 @@ module GraphQL
|
|
371
455
|
|
372
456
|
response_list
|
373
457
|
when "NON_NULL"
|
374
|
-
inner_type =
|
458
|
+
inner_type = current_type.of_type
|
375
459
|
# Don't `set_type_at_path` because we want the static type,
|
376
460
|
# we're going to use that to determine whether a `nil` should be propagated or not.
|
377
|
-
continue_field(path, value, field, inner_type, ast_node, next_selections, true, owner_object, arguments)
|
461
|
+
continue_field(path, value, owner_type, field, inner_type, ast_node, next_selections, true, owner_object, arguments)
|
378
462
|
else
|
379
|
-
raise "Invariant: Unhandled type kind #{
|
463
|
+
raise "Invariant: Unhandled type kind #{current_type.kind} (#{current_type})"
|
380
464
|
end
|
381
465
|
end
|
382
466
|
|
@@ -413,30 +497,39 @@ module GraphQL
|
|
413
497
|
true
|
414
498
|
end
|
415
499
|
|
500
|
+
def set_all_interpreter_context(object, field, arguments, path)
|
501
|
+
if object
|
502
|
+
@context[:current_object] = @interpreter_context[:current_object] = object
|
503
|
+
end
|
504
|
+
if field
|
505
|
+
@context[:current_field] = @interpreter_context[:current_field] = field
|
506
|
+
end
|
507
|
+
if arguments
|
508
|
+
@context[:current_arguments] = @interpreter_context[:current_arguments] = arguments
|
509
|
+
end
|
510
|
+
if path
|
511
|
+
@context[:current_path] = @interpreter_context[:current_path] = path
|
512
|
+
end
|
513
|
+
end
|
514
|
+
|
416
515
|
# @param obj [Object] Some user-returned value that may want to be batched
|
417
516
|
# @param path [Array<String>]
|
418
517
|
# @param field [GraphQL::Schema::Field]
|
419
518
|
# @param eager [Boolean] Set to `true` for mutation root fields only
|
420
519
|
# @param trace [Boolean] If `false`, don't wrap this with field tracing
|
421
520
|
# @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.
|
422
|
-
def after_lazy(lazy_obj, owner:, field:, path:, scoped_context:, owner_object:, arguments:, eager: false, trace: true, &block)
|
423
|
-
|
424
|
-
@interpreter_context[:current_arguments] = arguments
|
425
|
-
@interpreter_context[:current_path] = path
|
426
|
-
@interpreter_context[:current_field] = field
|
521
|
+
def after_lazy(lazy_obj, owner:, field:, path:, scoped_context:, owner_object:, arguments:, ast_node:, eager: false, trace: true, &block)
|
522
|
+
set_all_interpreter_context(owner_object, field, arguments, path)
|
427
523
|
if schema.lazy?(lazy_obj)
|
428
524
|
lazy = GraphQL::Execution::Lazy.new(path: path, field: field) do
|
429
|
-
|
430
|
-
@interpreter_context[:current_field] = field
|
431
|
-
@interpreter_context[:current_object] = owner_object
|
432
|
-
@interpreter_context[:current_arguments] = arguments
|
525
|
+
set_all_interpreter_context(owner_object, field, arguments, path)
|
433
526
|
context.scoped_context = scoped_context
|
434
527
|
# Wrap the execution of _this_ method with tracing,
|
435
528
|
# but don't wrap the continuation below
|
436
529
|
inner_obj = begin
|
437
530
|
query.with_error_handling do
|
438
531
|
if trace
|
439
|
-
query.trace("execute_field_lazy", {owner: owner, field: field, path: path, query: query, object: owner_object, arguments: arguments}) do
|
532
|
+
query.trace("execute_field_lazy", {owner: owner, field: field, path: path, query: query, object: owner_object, arguments: arguments, ast_node: ast_node}) do
|
440
533
|
schema.sync_lazy(lazy_obj)
|
441
534
|
end
|
442
535
|
else
|
@@ -446,7 +539,7 @@ module GraphQL
|
|
446
539
|
rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => err
|
447
540
|
err
|
448
541
|
end
|
449
|
-
after_lazy(inner_obj, owner: owner, field: field, path: path, scoped_context: context.scoped_context, owner_object: owner_object, arguments: arguments, eager: eager, trace: trace, &block)
|
542
|
+
after_lazy(inner_obj, owner: owner, field: field, path: path, ast_node: ast_node, scoped_context: context.scoped_context, owner_object: owner_object, arguments: arguments, eager: eager, trace: trace, &block)
|
450
543
|
end
|
451
544
|
|
452
545
|
if eager
|
@@ -461,9 +554,7 @@ module GraphQL
|
|
461
554
|
end
|
462
555
|
|
463
556
|
def arguments(graphql_object, arg_owner, ast_node)
|
464
|
-
|
465
|
-
if arg_owner.arguments_statically_coercible? &&
|
466
|
-
(!arg_owner.is_a?(GraphQL::Schema::Field) || (arg_owner.extras.empty? && arg_owner.extensions.empty?))
|
557
|
+
if arg_owner.arguments_statically_coercible?
|
467
558
|
query.arguments_for(ast_node, arg_owner)
|
468
559
|
else
|
469
560
|
# The arguments must be prepared in the context of the given object
|
@@ -504,6 +595,16 @@ module GraphQL
|
|
504
595
|
end
|
505
596
|
end
|
506
597
|
|
598
|
+
def value_at(path)
|
599
|
+
i = 0
|
600
|
+
value = @response.final_value
|
601
|
+
while value && (part = path[i])
|
602
|
+
value = value[part]
|
603
|
+
i += 1
|
604
|
+
end
|
605
|
+
value
|
606
|
+
end
|
607
|
+
|
507
608
|
# To propagate nulls, we have to know what the field type was
|
508
609
|
# at previous parts of the response.
|
509
610
|
# This hash matches the response
|
@@ -540,6 +641,18 @@ module GraphQL
|
|
540
641
|
res && res[:__dead]
|
541
642
|
end
|
542
643
|
|
644
|
+
# Set this pair in the Query context, but also in the interpeter namespace,
|
645
|
+
# for compatibility.
|
646
|
+
def set_interpreter_context(key, value)
|
647
|
+
@interpreter_context[key] = value
|
648
|
+
@context[key] = value
|
649
|
+
end
|
650
|
+
|
651
|
+
def delete_interpreter_context(key)
|
652
|
+
@interpreter_context.delete(key)
|
653
|
+
@context.delete(key)
|
654
|
+
end
|
655
|
+
|
543
656
|
def resolve_type(type, value, path)
|
544
657
|
trace_payload = { context: context, type: type, object: value, path: path }
|
545
658
|
resolved_type, resolved_value = query.trace("resolve_type", trace_payload) do
|