graphql 1.12.6 → 1.12.11
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_generator.rb +1 -1
- data/lib/generators/graphql/templates/graphql_controller.erb +2 -2
- data/lib/graphql.rb +10 -10
- data/lib/graphql/backtrace/table.rb +14 -2
- data/lib/graphql/dataloader.rb +44 -15
- data/lib/graphql/execution/errors.rb +109 -11
- data/lib/graphql/execution/execute.rb +1 -1
- data/lib/graphql/execution/interpreter.rb +4 -8
- data/lib/graphql/execution/interpreter/runtime.rb +207 -188
- data/lib/graphql/introspection.rb +1 -1
- data/lib/graphql/introspection/directive_type.rb +7 -3
- data/lib/graphql/introspection/schema_type.rb +1 -1
- data/lib/graphql/pagination/active_record_relation_connection.rb +7 -0
- data/lib/graphql/pagination/connections.rb +1 -1
- data/lib/graphql/pagination/relation_connection.rb +8 -1
- data/lib/graphql/query.rb +1 -3
- data/lib/graphql/query/null_context.rb +7 -1
- data/lib/graphql/query/validation_pipeline.rb +1 -1
- data/lib/graphql/rake_task.rb +3 -0
- data/lib/graphql/schema.rb +49 -237
- data/lib/graphql/schema/addition.rb +238 -0
- data/lib/graphql/schema/argument.rb +55 -36
- data/lib/graphql/schema/directive/transform.rb +13 -1
- data/lib/graphql/schema/input_object.rb +2 -2
- data/lib/graphql/schema/loader.rb +8 -0
- data/lib/graphql/schema/member/base_dsl_methods.rb +3 -15
- data/lib/graphql/schema/object.rb +19 -5
- data/lib/graphql/schema/resolver.rb +46 -24
- data/lib/graphql/schema/scalar.rb +3 -1
- data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +3 -1
- data/lib/graphql/static_validation/rules/argument_literals_are_compatible_error.rb +6 -2
- data/lib/graphql/static_validation/rules/arguments_are_defined.rb +2 -1
- data/lib/graphql/static_validation/rules/arguments_are_defined_error.rb +4 -2
- data/lib/graphql/static_validation/rules/directives_are_defined.rb +1 -1
- data/lib/graphql/static_validation/rules/fields_will_merge.rb +17 -8
- data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +1 -1
- data/lib/graphql/static_validation/validator.rb +5 -0
- data/lib/graphql/subscriptions/action_cable_subscriptions.rb +4 -3
- data/lib/graphql/subscriptions/broadcast_analyzer.rb +0 -3
- data/lib/graphql/subscriptions/serialize.rb +11 -1
- data/lib/graphql/types/relay/base_connection.rb +4 -0
- data/lib/graphql/types/relay/connection_behaviors.rb +21 -10
- data/lib/graphql/types/relay/edge_behaviors.rb +12 -1
- data/lib/graphql/version.rb +1 -1
- metadata +3 -3
- data/lib/graphql/execution/interpreter/hash_response.rb +0 -46
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9e46a535c108ff0ae582fcfa598b21cda8faad15142629b1cdae772f3d24cd7e
|
4
|
+
data.tar.gz: 3185ec249d624c6b23920d9d9fb47167ac031b7645053ff705e3c04acc0fa571
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 989c1074a6de63a31002d2bd92da719e1b08682d3eefe361add671a75c1991173b13cc6516f8ede8ff5bcf7499935e514cd8d7d57a2940e85160bc427538e99c
|
7
|
+
data.tar.gz: cb40ba585e721d28856951312755ef84bdd698a7299b1a383dd4f9db77d8b137e8d21ee9a8784b14780b4230a5a5325520d0ceadf90f6bd055781cc7159257b5
|
@@ -122,7 +122,7 @@ module Graphql
|
|
122
122
|
if options.api?
|
123
123
|
say("Skipped graphiql, as this rails project is API only")
|
124
124
|
say(" You may wish to use GraphiQL.app for development: https://github.com/skevy/graphiql-app")
|
125
|
-
elsif !options[:skip_graphiql]
|
125
|
+
elsif !options[:skip_graphiql] && !File.read(Rails.root.join("Gemfile")).include?("graphiql-rails")
|
126
126
|
gem("graphiql-rails", group: :development)
|
127
127
|
|
128
128
|
# This is a little cheat just to get cleaner shell output:
|
@@ -15,9 +15,9 @@ class GraphqlController < ApplicationController
|
|
15
15
|
}
|
16
16
|
result = <%= schema_name %>.execute(query, variables: variables, context: context, operation_name: operation_name)
|
17
17
|
render json: result
|
18
|
-
rescue => e
|
18
|
+
rescue StandardError => e
|
19
19
|
raise e unless Rails.env.development?
|
20
|
-
handle_error_in_development
|
20
|
+
handle_error_in_development(e)
|
21
21
|
end
|
22
22
|
|
23
23
|
private
|
data/lib/graphql.rb
CHANGED
@@ -81,10 +81,19 @@ end
|
|
81
81
|
# Order matters for these:
|
82
82
|
|
83
83
|
require "graphql/execution_error"
|
84
|
+
require "graphql/runtime_type_error"
|
85
|
+
require "graphql/unresolved_type_error"
|
86
|
+
require "graphql/invalid_null_error"
|
87
|
+
require "graphql/analysis_error"
|
88
|
+
require "graphql/coercion_error"
|
89
|
+
require "graphql/invalid_name_error"
|
90
|
+
require "graphql/integer_decoding_error"
|
91
|
+
require "graphql/integer_encoding_error"
|
92
|
+
require "graphql/string_encoding_error"
|
93
|
+
|
84
94
|
require "graphql/define"
|
85
95
|
require "graphql/base_type"
|
86
96
|
require "graphql/object_type"
|
87
|
-
|
88
97
|
require "graphql/enum_type"
|
89
98
|
require "graphql/input_object_type"
|
90
99
|
require "graphql/interface_type"
|
@@ -109,9 +118,6 @@ require "graphql/analysis"
|
|
109
118
|
require "graphql/tracing"
|
110
119
|
require "graphql/dig"
|
111
120
|
require "graphql/execution"
|
112
|
-
require "graphql/runtime_type_error"
|
113
|
-
require "graphql/unresolved_type_error"
|
114
|
-
require "graphql/invalid_null_error"
|
115
121
|
require "graphql/pagination"
|
116
122
|
require "graphql/schema"
|
117
123
|
require "graphql/query"
|
@@ -133,12 +139,6 @@ require "graphql/static_validation"
|
|
133
139
|
require "graphql/dataloader"
|
134
140
|
require "graphql/introspection"
|
135
141
|
|
136
|
-
require "graphql/analysis_error"
|
137
|
-
require "graphql/coercion_error"
|
138
|
-
require "graphql/invalid_name_error"
|
139
|
-
require "graphql/integer_decoding_error"
|
140
|
-
require "graphql/integer_encoding_error"
|
141
|
-
require "graphql/string_encoding_error"
|
142
142
|
require "graphql/version"
|
143
143
|
require "graphql/compatibility"
|
144
144
|
require "graphql/function"
|
@@ -83,7 +83,7 @@ module GraphQL
|
|
83
83
|
value = if top && @override_value
|
84
84
|
@override_value
|
85
85
|
else
|
86
|
-
@context.query.context.namespace(:interpreter)[:runtime]
|
86
|
+
value_at(@context.query.context.namespace(:interpreter)[:runtime], context_entry.path)
|
87
87
|
end
|
88
88
|
rows << [
|
89
89
|
"#{context_entry.ast_node ? context_entry.ast_node.position.join(":") : ""}",
|
@@ -130,7 +130,7 @@ module GraphQL
|
|
130
130
|
if object.is_a?(GraphQL::Schema::Object)
|
131
131
|
object = object.object
|
132
132
|
end
|
133
|
-
value = context_entry.namespace(:interpreter)[:runtime]
|
133
|
+
value = value_at(context_entry.namespace(:interpreter)[:runtime], [])
|
134
134
|
rows << [
|
135
135
|
"#{position}",
|
136
136
|
"#{op_type}#{op_name ? " #{op_name}" : ""}",
|
@@ -142,6 +142,18 @@ module GraphQL
|
|
142
142
|
raise "Unexpected get_rows subject #{context_entry.class} (#{context_entry.inspect})"
|
143
143
|
end
|
144
144
|
end
|
145
|
+
|
146
|
+
def value_at(runtime, path)
|
147
|
+
response = runtime.response
|
148
|
+
path.each do |key|
|
149
|
+
if response && (response = response[key])
|
150
|
+
next
|
151
|
+
else
|
152
|
+
break
|
153
|
+
end
|
154
|
+
end
|
155
|
+
response
|
156
|
+
end
|
145
157
|
end
|
146
158
|
end
|
147
159
|
end
|
data/lib/graphql/dataloader.rb
CHANGED
@@ -29,7 +29,12 @@ module GraphQL
|
|
29
29
|
|
30
30
|
def initialize
|
31
31
|
@source_cache = Hash.new { |h, source_class| h[source_class] = Hash.new { |h2, batch_parameters|
|
32
|
-
source =
|
32
|
+
source = if RUBY_VERSION < "3"
|
33
|
+
source_class.new(*batch_parameters)
|
34
|
+
else
|
35
|
+
batch_args, batch_kwargs = batch_parameters
|
36
|
+
source_class.new(*batch_args, **batch_kwargs)
|
37
|
+
end
|
33
38
|
source.setup(self)
|
34
39
|
h2[batch_parameters] = source
|
35
40
|
}
|
@@ -43,8 +48,15 @@ module GraphQL
|
|
43
48
|
# @param batch_parameters [Array<Object>]
|
44
49
|
# @return [GraphQL::Dataloader::Source] An instance of {source_class}, initialized with `self, *batch_parameters`,
|
45
50
|
# and cached for the lifetime of this {Multiplex}.
|
46
|
-
|
47
|
-
|
51
|
+
if RUBY_VERSION < "3"
|
52
|
+
def with(source_class, *batch_parameters)
|
53
|
+
@source_cache[source_class][batch_parameters]
|
54
|
+
end
|
55
|
+
else
|
56
|
+
def with(source_class, *batch_args, **batch_kwargs)
|
57
|
+
batch_parameters = [batch_args, batch_kwargs]
|
58
|
+
@source_cache[source_class][batch_parameters]
|
59
|
+
end
|
48
60
|
end
|
49
61
|
|
50
62
|
# Tell the dataloader that this fiber is waiting for data.
|
@@ -104,7 +116,7 @@ module GraphQL
|
|
104
116
|
while @pending_jobs.any?
|
105
117
|
# Create a Fiber to consume jobs until one of the jobs yields
|
106
118
|
# or jobs run out
|
107
|
-
f =
|
119
|
+
f = spawn_fiber {
|
108
120
|
while (job = @pending_jobs.shift)
|
109
121
|
job.call
|
110
122
|
end
|
@@ -124,26 +136,24 @@ module GraphQL
|
|
124
136
|
# This is where an evented approach would be even better -- can we tell which
|
125
137
|
# fibers are ready to continue, and continue execution there?
|
126
138
|
#
|
127
|
-
|
139
|
+
source_fiber_queue = if (first_source_fiber = create_source_fiber)
|
128
140
|
[first_source_fiber]
|
129
141
|
else
|
130
142
|
nil
|
131
143
|
end
|
132
144
|
|
133
|
-
if
|
134
|
-
|
135
|
-
# that newly-pending source will run _before_ the one that depends on it.
|
136
|
-
# (See below where the old fiber is pushed to the stack, then the new fiber is pushed on the stack.)
|
137
|
-
while (outer_source_fiber = source_fiber_stack.pop)
|
145
|
+
if source_fiber_queue
|
146
|
+
while (outer_source_fiber = source_fiber_queue.shift)
|
138
147
|
resume(outer_source_fiber)
|
139
148
|
|
140
|
-
if outer_source_fiber.alive?
|
141
|
-
source_fiber_stack << outer_source_fiber
|
142
|
-
end
|
143
149
|
# If this source caused more sources to become pending, run those before running this one again:
|
144
150
|
next_source_fiber = create_source_fiber
|
145
151
|
if next_source_fiber
|
146
|
-
|
152
|
+
source_fiber_queue << next_source_fiber
|
153
|
+
end
|
154
|
+
|
155
|
+
if outer_source_fiber.alive?
|
156
|
+
source_fiber_queue << outer_source_fiber
|
147
157
|
end
|
148
158
|
end
|
149
159
|
end
|
@@ -191,7 +201,7 @@ module GraphQL
|
|
191
201
|
#
|
192
202
|
# This design could probably be improved by maintaining a `@pending_sources` queue which is shared by the fibers,
|
193
203
|
# similar to `@pending_jobs`. That way, when a fiber is resumed, it would never pick up work that was finished by a different fiber.
|
194
|
-
source_fiber =
|
204
|
+
source_fiber = spawn_fiber do
|
195
205
|
pending_sources.each(&:run_pending_keys)
|
196
206
|
end
|
197
207
|
end
|
@@ -204,5 +214,24 @@ module GraphQL
|
|
204
214
|
rescue UncaughtThrowError => e
|
205
215
|
throw e.tag, e.value
|
206
216
|
end
|
217
|
+
|
218
|
+
# Copies the thread local vars into the fiber thread local vars. Many
|
219
|
+
# gems (such as RequestStore, MiniRacer, etc.) rely on thread local vars
|
220
|
+
# to keep track of execution context, and without this they do not
|
221
|
+
# behave as expected.
|
222
|
+
#
|
223
|
+
# @see https://github.com/rmosolgo/graphql-ruby/issues/3449
|
224
|
+
def spawn_fiber
|
225
|
+
fiber_locals = {}
|
226
|
+
|
227
|
+
Thread.current.keys.each do |fiber_var_key|
|
228
|
+
fiber_locals[fiber_var_key] = Thread.current[fiber_var_key]
|
229
|
+
end
|
230
|
+
|
231
|
+
Fiber.new do
|
232
|
+
fiber_locals.each { |k, v| Thread.current[k] = v }
|
233
|
+
yield
|
234
|
+
end
|
235
|
+
end
|
207
236
|
end
|
208
237
|
end
|
@@ -18,21 +18,83 @@ module GraphQL
|
|
18
18
|
#
|
19
19
|
class Errors
|
20
20
|
def self.use(schema)
|
21
|
-
|
22
|
-
|
23
|
-
GraphQL::Deprecation.warn("GraphQL::Execution::Errors is now installed by default, remove `use GraphQL::Execution::Errors` from #{definition_line}")
|
24
|
-
end
|
25
|
-
schema.error_handler = self.new(schema)
|
21
|
+
definition_line = caller(2, 1).first
|
22
|
+
GraphQL::Deprecation.warn("GraphQL::Execution::Errors is now installed by default, remove `use GraphQL::Execution::Errors` from #{definition_line}")
|
26
23
|
end
|
27
24
|
|
25
|
+
NEW_HANDLER_HASH = ->(h, k) {
|
26
|
+
h[k] = {
|
27
|
+
class: k,
|
28
|
+
handler: nil,
|
29
|
+
subclass_handlers: Hash.new(&NEW_HANDLER_HASH),
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
28
33
|
def initialize(schema)
|
29
34
|
@schema = schema
|
35
|
+
@handlers = {
|
36
|
+
class: nil,
|
37
|
+
handler: nil,
|
38
|
+
subclass_handlers: Hash.new(&NEW_HANDLER_HASH),
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
# @api private
|
43
|
+
def each_rescue
|
44
|
+
handlers = @handlers.values
|
45
|
+
while (handler = handlers.shift) do
|
46
|
+
yield(handler[:class], handler[:handler])
|
47
|
+
handlers.concat(handler[:subclass_handlers].values)
|
48
|
+
end
|
30
49
|
end
|
31
50
|
|
32
|
-
|
33
|
-
|
34
|
-
|
51
|
+
# Register this handler, updating the
|
52
|
+
# internal handler index to maintain least-to-most specific.
|
53
|
+
#
|
54
|
+
# @param error_class [Class<Exception>]
|
55
|
+
# @param error_handler [Proc]
|
56
|
+
# @return [void]
|
57
|
+
def rescue_from(error_class, error_handler)
|
58
|
+
subclasses_handlers = {}
|
59
|
+
this_level_subclasses = []
|
60
|
+
# During this traversal, do two things:
|
61
|
+
# - Identify any already-registered subclasses of this error class
|
62
|
+
# and gather them up to be inserted _under_ this class
|
63
|
+
# - Find the point in the index where this handler should be inserted
|
64
|
+
# (That is, _under_ any superclasses, or at top-level, if there are no superclasses registered)
|
65
|
+
handlers = @handlers[:subclass_handlers]
|
66
|
+
while (handlers) do
|
67
|
+
this_level_subclasses.clear
|
68
|
+
# First, identify already-loaded handlers that belong
|
69
|
+
# _under_ this one. (That is, they're handlers
|
70
|
+
# for subclasses of `error_class`.)
|
71
|
+
handlers.each do |err_class, handler|
|
72
|
+
if err_class < error_class
|
73
|
+
subclasses_handlers[err_class] = handler
|
74
|
+
this_level_subclasses << err_class
|
75
|
+
end
|
76
|
+
end
|
77
|
+
# Any handlers that we'll be moving, delete them from this point in the index
|
78
|
+
this_level_subclasses.each do |err_class|
|
79
|
+
handlers.delete(err_class)
|
80
|
+
end
|
81
|
+
|
82
|
+
# See if any keys in this hash are superclasses of this new class:
|
83
|
+
next_index_point = handlers.find { |err_class, handler| error_class < err_class }
|
84
|
+
if next_index_point
|
85
|
+
handlers = next_index_point[1][:subclass_handlers]
|
86
|
+
else
|
87
|
+
# this new handler doesn't belong to any sub-handlers,
|
88
|
+
# so insert it in the current set of `handlers`
|
89
|
+
break
|
90
|
+
end
|
35
91
|
end
|
92
|
+
# Having found the point at which to insert this handler,
|
93
|
+
# register it and merge any subclass handlers back in at this point.
|
94
|
+
this_class_handlers = handlers[error_class]
|
95
|
+
this_class_handlers[:handler] = error_handler
|
96
|
+
this_class_handlers[:subclass_handlers].merge!(subclasses_handlers)
|
97
|
+
nil
|
36
98
|
end
|
37
99
|
|
38
100
|
# Call the given block with the schema's configured error handlers.
|
@@ -44,8 +106,7 @@ module GraphQL
|
|
44
106
|
def with_error_handling(ctx)
|
45
107
|
yield
|
46
108
|
rescue StandardError => err
|
47
|
-
|
48
|
-
_err_class, handler = rescues.find { |err_class, handler| err.is_a?(err_class) }
|
109
|
+
handler = find_handler_for(err.class)
|
49
110
|
if handler
|
50
111
|
runtime_info = ctx.namespace(:interpreter) || {}
|
51
112
|
obj = runtime_info[:current_object]
|
@@ -54,11 +115,48 @@ module GraphQL
|
|
54
115
|
if obj.is_a?(GraphQL::Schema::Object)
|
55
116
|
obj = obj.object
|
56
117
|
end
|
57
|
-
handler.call(err, obj, args, ctx, field)
|
118
|
+
handler[:handler].call(err, obj, args, ctx, field)
|
58
119
|
else
|
59
120
|
raise err
|
60
121
|
end
|
61
122
|
end
|
123
|
+
|
124
|
+
# @return [Proc, nil] The handler for `error_class`, if one was registered on this schema or inherited
|
125
|
+
def find_handler_for(error_class)
|
126
|
+
handlers = @handlers[:subclass_handlers]
|
127
|
+
handler = nil
|
128
|
+
while (handlers) do
|
129
|
+
_err_class, next_handler = handlers.find { |err_class, handler| error_class <= err_class }
|
130
|
+
if next_handler
|
131
|
+
handlers = next_handler[:subclass_handlers]
|
132
|
+
handler = next_handler
|
133
|
+
else
|
134
|
+
# Don't reassign `handler` --
|
135
|
+
# let the previous assignment carry over outside this block.
|
136
|
+
break
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# check for a handler from a parent class:
|
141
|
+
if @schema.superclass.respond_to?(:error_handler) && (parent_errors = @schema.superclass.error_handler)
|
142
|
+
parent_handler = parent_errors.find_handler_for(error_class)
|
143
|
+
end
|
144
|
+
|
145
|
+
# If the inherited handler is more specific than the one defined here,
|
146
|
+
# use it.
|
147
|
+
# If it's a tie (or there is no parent handler), use the one defined here.
|
148
|
+
# If there's an inherited one, but not one defined here, use the inherited one.
|
149
|
+
# Otherwise, there's no handler for this error, return `nil`.
|
150
|
+
if parent_handler && handler && parent_handler[:class] < handler[:class]
|
151
|
+
parent_handler
|
152
|
+
elsif handler
|
153
|
+
handler
|
154
|
+
elsif parent_handler
|
155
|
+
parent_handler
|
156
|
+
else
|
157
|
+
nil
|
158
|
+
end
|
159
|
+
end
|
62
160
|
end
|
63
161
|
end
|
64
162
|
end
|
@@ -4,7 +4,6 @@ require "graphql/execution/interpreter/argument_value"
|
|
4
4
|
require "graphql/execution/interpreter/arguments"
|
5
5
|
require "graphql/execution/interpreter/arguments_cache"
|
6
6
|
require "graphql/execution/interpreter/execution_errors"
|
7
|
-
require "graphql/execution/interpreter/hash_response"
|
8
7
|
require "graphql/execution/interpreter/runtime"
|
9
8
|
require "graphql/execution/interpreter/resolve"
|
10
9
|
require "graphql/execution/interpreter/handles_raw_value"
|
@@ -19,7 +18,7 @@ module GraphQL
|
|
19
18
|
def execute(_operation, _root_type, query)
|
20
19
|
runtime = evaluate(query)
|
21
20
|
sync_lazies(query: query)
|
22
|
-
runtime.
|
21
|
+
runtime.response
|
23
22
|
end
|
24
23
|
|
25
24
|
def self.use(schema_class)
|
@@ -57,7 +56,7 @@ module GraphQL
|
|
57
56
|
|
58
57
|
def self.finish_query(query, _multiplex)
|
59
58
|
{
|
60
|
-
"data" => query.context.namespace(:interpreter)[:runtime].
|
59
|
+
"data" => query.context.namespace(:interpreter)[:runtime].response
|
61
60
|
}
|
62
61
|
end
|
63
62
|
|
@@ -67,10 +66,7 @@ module GraphQL
|
|
67
66
|
# Although queries in a multiplex _share_ an Interpreter instance,
|
68
67
|
# they also have another item of state, which is private to that query
|
69
68
|
# in particular, assign it here:
|
70
|
-
runtime = Runtime.new(
|
71
|
-
query: query,
|
72
|
-
response: HashResponse.new,
|
73
|
-
)
|
69
|
+
runtime = Runtime.new(query: query)
|
74
70
|
query.context.namespace(:interpreter)[:runtime] = runtime
|
75
71
|
|
76
72
|
query.trace("execute_query", {query: query}) do
|
@@ -91,7 +87,7 @@ module GraphQL
|
|
91
87
|
final_values = queries.map do |query|
|
92
88
|
runtime = query.context.namespace(:interpreter)[:runtime]
|
93
89
|
# it might not be present if the query has an error
|
94
|
-
runtime ? runtime.
|
90
|
+
runtime ? runtime.response : nil
|
95
91
|
end
|
96
92
|
final_values.compact!
|
97
93
|
tracer.trace("execute_query_lazy", {multiplex: multiplex, query: query}) do
|
@@ -8,6 +8,28 @@ module GraphQL
|
|
8
8
|
#
|
9
9
|
# @api private
|
10
10
|
class Runtime
|
11
|
+
|
12
|
+
module GraphQLResult
|
13
|
+
# These methods are private concerns of GraphQL-Ruby,
|
14
|
+
# they aren't guaranteed to continue working in the future.
|
15
|
+
attr_accessor :graphql_dead, :graphql_parent, :graphql_result_name
|
16
|
+
# Although these are used by only one of the Result classes,
|
17
|
+
# it's handy to have the methods implemented on both (even though they just return `nil`)
|
18
|
+
# because it makes it easy to check if anything is assigned.
|
19
|
+
# @return [nil, Array<String>]
|
20
|
+
attr_accessor :graphql_non_null_field_names
|
21
|
+
# @return [nil, true]
|
22
|
+
attr_accessor :graphql_non_null_list_items
|
23
|
+
end
|
24
|
+
|
25
|
+
class GraphQLResultHash < Hash
|
26
|
+
include GraphQLResult
|
27
|
+
end
|
28
|
+
|
29
|
+
class GraphQLResultArray < Array
|
30
|
+
include GraphQLResult
|
31
|
+
end
|
32
|
+
|
11
33
|
# @return [GraphQL::Query]
|
12
34
|
attr_reader :query
|
13
35
|
|
@@ -17,24 +39,23 @@ module GraphQL
|
|
17
39
|
# @return [GraphQL::Query::Context]
|
18
40
|
attr_reader :context
|
19
41
|
|
20
|
-
|
42
|
+
# @return [Hash]
|
43
|
+
attr_reader :response
|
44
|
+
|
45
|
+
def initialize(query:)
|
21
46
|
@query = query
|
22
47
|
@dataloader = query.multiplex.dataloader
|
23
48
|
@schema = query.schema
|
24
49
|
@context = query.context
|
25
50
|
@multiplex_context = query.multiplex.context
|
26
51
|
@interpreter_context = @context.namespace(:interpreter)
|
27
|
-
@response =
|
28
|
-
@dead_paths = {}
|
29
|
-
@types_at_paths = {}
|
52
|
+
@response = GraphQLResultHash.new
|
30
53
|
# A cache of { Class => { String => Schema::Field } }
|
31
54
|
# Which assumes that MyObject.get_field("myField") will return the same field
|
32
55
|
# during the lifetime of a query
|
33
56
|
@fields_cache = Hash.new { |h, k| h[k] = {} }
|
34
|
-
|
35
|
-
|
36
|
-
def final_value
|
37
|
-
@response.final_value
|
57
|
+
# { Class => Boolean }
|
58
|
+
@lazy_cache = {}
|
38
59
|
end
|
39
60
|
|
40
61
|
def inspect
|
@@ -50,24 +71,28 @@ module GraphQL
|
|
50
71
|
root_type = schema.root_type_for_operation(root_op_type)
|
51
72
|
path = []
|
52
73
|
set_all_interpreter_context(query.root_value, nil, nil, path)
|
53
|
-
object_proxy = authorized_new(root_type, query.root_value, context
|
74
|
+
object_proxy = authorized_new(root_type, query.root_value, context)
|
54
75
|
object_proxy = schema.sync_lazy(object_proxy)
|
76
|
+
|
55
77
|
if object_proxy.nil?
|
56
78
|
# Root .authorized? returned false.
|
57
|
-
|
79
|
+
@response = nil
|
58
80
|
else
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
81
|
+
resolve_with_directives(object_proxy, root_operation) do # execute query level directives
|
82
|
+
gathered_selections = gather_selections(object_proxy, root_type, root_operation.selections)
|
83
|
+
# Make the first fiber which will begin execution
|
84
|
+
@dataloader.append_job {
|
85
|
+
evaluate_selections(
|
86
|
+
path,
|
87
|
+
context.scoped_context,
|
88
|
+
object_proxy,
|
89
|
+
root_type,
|
90
|
+
root_op_type == "mutation",
|
91
|
+
gathered_selections,
|
92
|
+
@response,
|
93
|
+
)
|
94
|
+
}
|
95
|
+
end
|
71
96
|
end
|
72
97
|
delete_interpreter_context(:current_path)
|
73
98
|
delete_interpreter_context(:current_field)
|
@@ -134,13 +159,13 @@ module GraphQL
|
|
134
159
|
NO_ARGS = {}.freeze
|
135
160
|
|
136
161
|
# @return [void]
|
137
|
-
def evaluate_selections(path, scoped_context, owner_object, owner_type, is_eager_selection, gathered_selections)
|
162
|
+
def evaluate_selections(path, scoped_context, owner_object, owner_type, is_eager_selection, gathered_selections, selections_result)
|
138
163
|
set_all_interpreter_context(owner_object, nil, nil, path)
|
139
164
|
|
140
165
|
gathered_selections.each do |result_name, field_ast_nodes_or_ast_node|
|
141
166
|
@dataloader.append_job {
|
142
167
|
evaluate_selection(
|
143
|
-
path, result_name, field_ast_nodes_or_ast_node, scoped_context, owner_object, owner_type, is_eager_selection
|
168
|
+
path, result_name, field_ast_nodes_or_ast_node, scoped_context, owner_object, owner_type, is_eager_selection, selections_result
|
144
169
|
)
|
145
170
|
}
|
146
171
|
end
|
@@ -151,7 +176,7 @@ module GraphQL
|
|
151
176
|
attr_reader :progress_path
|
152
177
|
|
153
178
|
# @return [void]
|
154
|
-
def evaluate_selection(path, result_name, field_ast_nodes_or_ast_node, scoped_context, owner_object, owner_type, is_eager_field)
|
179
|
+
def evaluate_selection(path, result_name, field_ast_nodes_or_ast_node, scoped_context, owner_object, owner_type, is_eager_field, selections_result) # rubocop:disable Metrics/ParameterLists
|
155
180
|
# As a performance optimization, the hash key will be a `Node` if
|
156
181
|
# there's only one selection of the field. But if there are multiple
|
157
182
|
# selections of the field, it will be an Array of nodes
|
@@ -185,7 +210,9 @@ module GraphQL
|
|
185
210
|
# This seems janky, but we need to know
|
186
211
|
# the field's return type at this path in order
|
187
212
|
# to propagate `null`
|
188
|
-
|
213
|
+
if return_type.non_null?
|
214
|
+
(selections_result.graphql_non_null_field_names ||= []).push(result_name)
|
215
|
+
end
|
189
216
|
# Set this before calling `run_with_directives`, so that the directive can have the latest path
|
190
217
|
set_all_interpreter_context(nil, field_defn, nil, next_path)
|
191
218
|
|
@@ -193,27 +220,27 @@ module GraphQL
|
|
193
220
|
object = owner_object
|
194
221
|
|
195
222
|
if is_introspection
|
196
|
-
object = authorized_new(field_defn.owner, object, context
|
223
|
+
object = authorized_new(field_defn.owner, object, context)
|
197
224
|
end
|
198
225
|
|
199
226
|
total_args_count = field_defn.arguments.size
|
200
227
|
if total_args_count == 0
|
201
228
|
kwarg_arguments = GraphQL::Execution::Interpreter::Arguments::EMPTY
|
202
|
-
evaluate_selection_with_args(kwarg_arguments, field_defn, next_path, ast_node, field_ast_nodes, scoped_context, owner_type, object, is_eager_field)
|
229
|
+
evaluate_selection_with_args(kwarg_arguments, field_defn, next_path, ast_node, field_ast_nodes, scoped_context, owner_type, object, is_eager_field, result_name, selections_result)
|
203
230
|
else
|
204
231
|
# TODO remove all arguments(...) usages?
|
205
232
|
@query.arguments_cache.dataload_for(ast_node, field_defn, object) do |resolved_arguments|
|
206
|
-
evaluate_selection_with_args(resolved_arguments, field_defn, next_path, ast_node, field_ast_nodes, scoped_context, owner_type, object, is_eager_field)
|
233
|
+
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)
|
207
234
|
end
|
208
235
|
end
|
209
236
|
end
|
210
237
|
|
211
|
-
def evaluate_selection_with_args(kwarg_arguments, field_defn, next_path, ast_node, field_ast_nodes, scoped_context, owner_type, object, is_eager_field) # rubocop:disable Metrics/ParameterLists
|
238
|
+
def evaluate_selection_with_args(kwarg_arguments, field_defn, next_path, ast_node, field_ast_nodes, scoped_context, owner_type, object, is_eager_field, result_name, selection_result) # rubocop:disable Metrics/ParameterLists
|
212
239
|
context.scoped_context = scoped_context
|
213
240
|
return_type = field_defn.type
|
214
|
-
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|
|
241
|
+
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, result_name: result_name, result: selection_result) do |resolved_arguments|
|
215
242
|
if resolved_arguments.is_a?(GraphQL::ExecutionError) || resolved_arguments.is_a?(GraphQL::UnauthorizedError)
|
216
|
-
continue_value(next_path, resolved_arguments, owner_type, field_defn, return_type.non_null?, ast_node)
|
243
|
+
continue_value(next_path, resolved_arguments, owner_type, field_defn, return_type.non_null?, ast_node, result_name, selection_result)
|
217
244
|
next
|
218
245
|
end
|
219
246
|
|
@@ -246,11 +273,17 @@ module GraphQL
|
|
246
273
|
# Use this flag to tell Interpreter::Arguments to add itself
|
247
274
|
# to the keyword args hash _before_ freezing everything.
|
248
275
|
extra_args[:argument_details] = :__arguments_add_self
|
276
|
+
when :irep_node
|
277
|
+
# This is used by `__typename` in order to support the legacy runtime,
|
278
|
+
# but it has no use here (and it's always `nil`).
|
279
|
+
# Stop adding it here to avoid the overhead of `.merge_extras` below.
|
249
280
|
else
|
250
281
|
extra_args[extra] = field_defn.fetch_extra(extra, context)
|
251
282
|
end
|
252
283
|
end
|
253
|
-
|
284
|
+
if extra_args.any?
|
285
|
+
resolved_arguments = resolved_arguments.merge_extras(extra_args)
|
286
|
+
end
|
254
287
|
resolved_arguments.keyword_arguments
|
255
288
|
end
|
256
289
|
|
@@ -275,13 +308,10 @@ module GraphQL
|
|
275
308
|
rescue GraphQL::ExecutionError => err
|
276
309
|
err
|
277
310
|
end
|
278
|
-
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|
|
279
|
-
continue_value = continue_value(next_path, inner_result, owner_type, field_defn, return_type.non_null?, ast_node)
|
280
|
-
if
|
281
|
-
|
282
|
-
write_in_response(next_path, continue_value.resolve)
|
283
|
-
elsif HALT != continue_value
|
284
|
-
continue_field(next_path, continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, kwarg_arguments)
|
311
|
+
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, result_name: result_name, result: selection_result) do |inner_result|
|
312
|
+
continue_value = continue_value(next_path, inner_result, owner_type, field_defn, return_type.non_null?, ast_node, result_name, selection_result)
|
313
|
+
if HALT != continue_value
|
314
|
+
continue_field(next_path, continue_value, owner_type, field_defn, return_type, ast_node, next_selections, false, object, kwarg_arguments, result_name, selection_result)
|
285
315
|
end
|
286
316
|
end
|
287
317
|
end
|
@@ -298,39 +328,109 @@ module GraphQL
|
|
298
328
|
end
|
299
329
|
end
|
300
330
|
|
331
|
+
def dead_result?(selection_result)
|
332
|
+
r = selection_result
|
333
|
+
while r
|
334
|
+
if r.graphql_dead
|
335
|
+
return true
|
336
|
+
else
|
337
|
+
r = r.graphql_parent
|
338
|
+
end
|
339
|
+
end
|
340
|
+
false
|
341
|
+
end
|
342
|
+
|
343
|
+
def set_result(selection_result, result_name, value)
|
344
|
+
if !dead_result?(selection_result)
|
345
|
+
if value.nil? &&
|
346
|
+
( # there are two conditions under which `nil` is not allowed in the response:
|
347
|
+
(selection_result.graphql_non_null_list_items) || # this value would be written into a list that doesn't allow nils
|
348
|
+
((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
|
349
|
+
)
|
350
|
+
# This is an invalid nil that should be propagated
|
351
|
+
# One caller of this method passes a block,
|
352
|
+
# namely when application code returns a `nil` to GraphQL and it doesn't belong there.
|
353
|
+
# The other possibility for reaching here is when a field returns an ExecutionError, so we write
|
354
|
+
# `nil` to the response, not knowing whether it's an invalid `nil` or not.
|
355
|
+
# (And in that case, we don't have to call the schema's handler, since it's not a bug in the application.)
|
356
|
+
# TODO the code is trying to tell me something.
|
357
|
+
yield if block_given?
|
358
|
+
parent = selection_result.graphql_parent
|
359
|
+
name_in_parent = selection_result.graphql_result_name
|
360
|
+
if parent.nil? # This is a top-level result hash
|
361
|
+
@response = nil
|
362
|
+
else
|
363
|
+
set_result(parent, name_in_parent, nil)
|
364
|
+
# This is odd, but it's how it used to work. Even if `parent` _would_ accept
|
365
|
+
# a `nil`, it's marked dead. TODO: check the spec, is there a reason for this?
|
366
|
+
parent.graphql_dead = true
|
367
|
+
end
|
368
|
+
else
|
369
|
+
selection_result[result_name] = value
|
370
|
+
end
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
301
374
|
HALT = Object.new
|
302
|
-
def continue_value(path, value, parent_type, field, is_non_null, ast_node)
|
303
|
-
|
375
|
+
def continue_value(path, value, parent_type, field, is_non_null, ast_node, result_name, selection_result) # rubocop:disable Metrics/ParameterLists
|
376
|
+
case value
|
377
|
+
when nil
|
304
378
|
if is_non_null
|
305
|
-
|
306
|
-
|
379
|
+
set_result(selection_result, result_name, nil) do
|
380
|
+
# This block is called if `result_name` is not dead. (Maybe a previous invalid nil caused it be marked dead.)
|
381
|
+
err = parent_type::InvalidNullError.new(parent_type, field, value)
|
382
|
+
schema.type_error(err, context)
|
383
|
+
end
|
307
384
|
else
|
308
|
-
|
385
|
+
set_result(selection_result, result_name, nil)
|
309
386
|
end
|
310
387
|
HALT
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
388
|
+
when GraphQL::Error
|
389
|
+
# Handle these cases inside a single `when`
|
390
|
+
# to avoid the overhead of checking three different classes
|
391
|
+
# every time.
|
392
|
+
if value.is_a?(GraphQL::ExecutionError)
|
393
|
+
if !dead_result?(selection_result)
|
394
|
+
value.path ||= path
|
395
|
+
value.ast_node ||= ast_node
|
396
|
+
context.errors << value
|
397
|
+
set_result(selection_result, result_name, nil)
|
398
|
+
end
|
399
|
+
HALT
|
400
|
+
elsif value.is_a?(GraphQL::UnauthorizedError)
|
401
|
+
# this hook might raise & crash, or it might return
|
402
|
+
# a replacement value
|
403
|
+
next_value = begin
|
404
|
+
schema.unauthorized_object(value)
|
405
|
+
rescue GraphQL::ExecutionError => err
|
406
|
+
err
|
407
|
+
end
|
408
|
+
continue_value(path, next_value, parent_type, field, is_non_null, ast_node, result_name, selection_result)
|
409
|
+
elsif GraphQL::Execution::Execute::SKIP == value
|
410
|
+
HALT
|
411
|
+
else
|
412
|
+
# What could this actually _be_? Anyhow,
|
413
|
+
# preserve the default behavior of doing nothing with it.
|
414
|
+
value
|
320
415
|
end
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
416
|
+
when Array
|
417
|
+
# It's an array full of execution errors; add them all.
|
418
|
+
if value.any? && value.all? { |v| v.is_a?(GraphQL::ExecutionError) }
|
419
|
+
if !dead_result?(selection_result)
|
420
|
+
value.each_with_index do |error, index|
|
421
|
+
error.ast_node ||= ast_node
|
422
|
+
error.path ||= path + (field.type.list? ? [index] : [])
|
423
|
+
context.errors << error
|
424
|
+
end
|
425
|
+
set_result(selection_result, result_name, nil)
|
426
|
+
end
|
427
|
+
HALT
|
428
|
+
else
|
429
|
+
value
|
330
430
|
end
|
331
|
-
|
332
|
-
|
333
|
-
|
431
|
+
when GraphQL::Execution::Interpreter::RawValue
|
432
|
+
# Write raw value directly to the response without resolving nested objects
|
433
|
+
set_result(selection_result, result_name, value.resolve)
|
334
434
|
HALT
|
335
435
|
else
|
336
436
|
value
|
@@ -345,17 +445,22 @@ module GraphQL
|
|
345
445
|
# Location information from `path` and `ast_node`.
|
346
446
|
#
|
347
447
|
# @return [Lazy, Array, Hash, Object] Lazy, Array, and Hash are all traversed to resolve lazy values later
|
348
|
-
def continue_field(path, value, owner_type, field, current_type, ast_node, next_selections, is_non_null, owner_object, arguments) # rubocop:disable Metrics/ParameterLists
|
448
|
+
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
|
449
|
+
if current_type.non_null?
|
450
|
+
current_type = current_type.of_type
|
451
|
+
is_non_null = true
|
452
|
+
end
|
453
|
+
|
349
454
|
case current_type.kind.name
|
350
455
|
when "SCALAR", "ENUM"
|
351
456
|
r = current_type.coerce_result(value, context)
|
352
|
-
|
457
|
+
set_result(selection_result, result_name, r)
|
353
458
|
r
|
354
459
|
when "UNION", "INTERFACE"
|
355
460
|
resolved_type_or_lazy, resolved_value = resolve_type(current_type, value, path)
|
356
461
|
resolved_value ||= value
|
357
462
|
|
358
|
-
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|
|
463
|
+
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|
|
359
464
|
possible_types = query.possible_types(current_type)
|
360
465
|
|
361
466
|
if !possible_types.include?(resolved_type)
|
@@ -363,46 +468,52 @@ module GraphQL
|
|
363
468
|
err_class = current_type::UnresolvedTypeError
|
364
469
|
type_error = err_class.new(resolved_value, field, parent_type, resolved_type, possible_types)
|
365
470
|
schema.type_error(type_error, context)
|
366
|
-
|
471
|
+
set_result(selection_result, result_name, nil)
|
367
472
|
nil
|
368
473
|
else
|
369
|
-
continue_field(path, resolved_value, owner_type, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments)
|
474
|
+
continue_field(path, resolved_value, owner_type, field, resolved_type, ast_node, next_selections, is_non_null, owner_object, arguments, result_name, selection_result)
|
370
475
|
end
|
371
476
|
end
|
372
477
|
when "OBJECT"
|
373
478
|
object_proxy = begin
|
374
|
-
authorized_new(current_type, value, context
|
479
|
+
authorized_new(current_type, value, context)
|
375
480
|
rescue GraphQL::ExecutionError => err
|
376
481
|
err
|
377
482
|
end
|
378
|
-
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|
|
379
|
-
continue_value = continue_value(path, inner_object, owner_type, field, is_non_null, ast_node)
|
483
|
+
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|
|
484
|
+
continue_value = continue_value(path, inner_object, owner_type, field, is_non_null, ast_node, result_name, selection_result)
|
380
485
|
if HALT != continue_value
|
381
|
-
response_hash =
|
382
|
-
|
486
|
+
response_hash = GraphQLResultHash.new
|
487
|
+
response_hash.graphql_parent = selection_result
|
488
|
+
response_hash.graphql_result_name = result_name
|
489
|
+
set_result(selection_result, result_name, response_hash)
|
383
490
|
gathered_selections = gather_selections(continue_value, current_type, next_selections)
|
384
|
-
evaluate_selections(path, context.scoped_context, continue_value, current_type, false, gathered_selections)
|
491
|
+
evaluate_selections(path, context.scoped_context, continue_value, current_type, false, gathered_selections, response_hash)
|
385
492
|
response_hash
|
386
493
|
end
|
387
494
|
end
|
388
495
|
when "LIST"
|
389
|
-
response_list = []
|
390
|
-
write_in_response(path, response_list)
|
391
496
|
inner_type = current_type.of_type
|
497
|
+
response_list = GraphQLResultArray.new
|
498
|
+
response_list.graphql_non_null_list_items = inner_type.non_null?
|
499
|
+
response_list.graphql_parent = selection_result
|
500
|
+
response_list.graphql_result_name = result_name
|
501
|
+
set_result(selection_result, result_name, response_list)
|
502
|
+
|
392
503
|
idx = 0
|
393
504
|
scoped_context = context.scoped_context
|
394
505
|
begin
|
395
506
|
value.each do |inner_value|
|
396
507
|
next_path = path.dup
|
397
508
|
next_path << idx
|
509
|
+
this_idx = idx
|
398
510
|
next_path.freeze
|
399
511
|
idx += 1
|
400
|
-
set_type_at_path(next_path, inner_type)
|
401
512
|
# This will update `response_list` with the lazy
|
402
|
-
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|
|
403
|
-
continue_value = continue_value(next_path, inner_inner_value, owner_type, field, inner_type.non_null?, ast_node)
|
513
|
+
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|
|
514
|
+
continue_value = continue_value(next_path, inner_inner_value, owner_type, field, inner_type.non_null?, ast_node, this_idx, response_list)
|
404
515
|
if HALT != continue_value
|
405
|
-
continue_field(next_path, continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments)
|
516
|
+
continue_field(next_path, continue_value, owner_type, field, inner_type, ast_node, next_selections, false, owner_object, arguments, this_idx, response_list)
|
406
517
|
end
|
407
518
|
end
|
408
519
|
end
|
@@ -418,11 +529,6 @@ module GraphQL
|
|
418
529
|
end
|
419
530
|
|
420
531
|
response_list
|
421
|
-
when "NON_NULL"
|
422
|
-
inner_type = current_type.of_type
|
423
|
-
# Don't `set_type_at_path` because we want the static type,
|
424
|
-
# we're going to use that to determine whether a `nil` should be propagated or not.
|
425
|
-
continue_field(path, value, owner_type, field, inner_type, ast_node, next_selections, true, owner_object, arguments)
|
426
532
|
else
|
427
533
|
raise "Invariant: Unhandled type kind #{current_type.kind} (#{current_type})"
|
428
534
|
end
|
@@ -482,9 +588,8 @@ module GraphQL
|
|
482
588
|
# @param eager [Boolean] Set to `true` for mutation root fields only
|
483
589
|
# @param trace [Boolean] If `false`, don't wrap this with field tracing
|
484
590
|
# @return [GraphQL::Execution::Lazy, Object] If loading `object` will be deferred, it's a wrapper over it.
|
485
|
-
def after_lazy(lazy_obj, owner:, field:, path:, scoped_context:, owner_object:, arguments:, ast_node:, eager: false, trace: true, &block)
|
486
|
-
|
487
|
-
if schema.lazy?(lazy_obj)
|
591
|
+
def after_lazy(lazy_obj, owner:, field:, path:, scoped_context:, owner_object:, arguments:, ast_node:, result:, result_name:, eager: false, trace: true, &block)
|
592
|
+
if lazy?(lazy_obj)
|
488
593
|
lazy = GraphQL::Execution::Lazy.new(path: path, field: field) do
|
489
594
|
set_all_interpreter_context(owner_object, field, arguments, path)
|
490
595
|
context.scoped_context = scoped_context
|
@@ -503,16 +608,17 @@ module GraphQL
|
|
503
608
|
rescue GraphQL::ExecutionError, GraphQL::UnauthorizedError => err
|
504
609
|
err
|
505
610
|
end
|
506
|
-
|
611
|
+
yield(inner_obj)
|
507
612
|
end
|
508
613
|
|
509
614
|
if eager
|
510
615
|
lazy.value
|
511
616
|
else
|
512
|
-
|
617
|
+
set_result(result, result_name, lazy)
|
513
618
|
lazy
|
514
619
|
end
|
515
620
|
else
|
621
|
+
set_all_interpreter_context(owner_object, field, arguments, path)
|
516
622
|
yield(lazy_obj)
|
517
623
|
end
|
518
624
|
end
|
@@ -526,85 +632,6 @@ module GraphQL
|
|
526
632
|
end
|
527
633
|
end
|
528
634
|
|
529
|
-
def write_invalid_null_in_response(path, invalid_null_error)
|
530
|
-
if !dead_path?(path)
|
531
|
-
schema.type_error(invalid_null_error, context)
|
532
|
-
write_in_response(path, nil)
|
533
|
-
add_dead_path(path)
|
534
|
-
end
|
535
|
-
end
|
536
|
-
|
537
|
-
def write_execution_errors_in_response(path, errors)
|
538
|
-
if !dead_path?(path)
|
539
|
-
errors.each do |v|
|
540
|
-
context.errors << v
|
541
|
-
end
|
542
|
-
write_in_response(path, nil)
|
543
|
-
add_dead_path(path)
|
544
|
-
end
|
545
|
-
end
|
546
|
-
|
547
|
-
def write_in_response(path, value)
|
548
|
-
if dead_path?(path)
|
549
|
-
return
|
550
|
-
else
|
551
|
-
if value.nil? && path.any? && type_at(path).non_null?
|
552
|
-
# This nil is invalid, try writing it at the previous spot
|
553
|
-
propagate_path = path[0..-2]
|
554
|
-
write_in_response(propagate_path, value)
|
555
|
-
add_dead_path(propagate_path)
|
556
|
-
else
|
557
|
-
@response.write(path, value)
|
558
|
-
end
|
559
|
-
end
|
560
|
-
end
|
561
|
-
|
562
|
-
def value_at(path)
|
563
|
-
i = 0
|
564
|
-
value = @response.final_value
|
565
|
-
while value && (part = path[i])
|
566
|
-
value = value[part]
|
567
|
-
i += 1
|
568
|
-
end
|
569
|
-
value
|
570
|
-
end
|
571
|
-
|
572
|
-
# To propagate nulls, we have to know what the field type was
|
573
|
-
# at previous parts of the response.
|
574
|
-
# This hash matches the response
|
575
|
-
def type_at(path)
|
576
|
-
@types_at_paths.fetch(path)
|
577
|
-
end
|
578
|
-
|
579
|
-
def set_type_at_path(path, type)
|
580
|
-
@types_at_paths[path] = type
|
581
|
-
nil
|
582
|
-
end
|
583
|
-
|
584
|
-
# Mark `path` as having been permanently nulled out.
|
585
|
-
# No values will be added beyond that path.
|
586
|
-
def add_dead_path(path)
|
587
|
-
dead = @dead_paths
|
588
|
-
path.each do |part|
|
589
|
-
dead = dead[part] ||= {}
|
590
|
-
end
|
591
|
-
dead[:__dead] = true
|
592
|
-
end
|
593
|
-
|
594
|
-
def dead_path?(path)
|
595
|
-
res = @dead_paths
|
596
|
-
path.each do |part|
|
597
|
-
if res
|
598
|
-
if res[:__dead]
|
599
|
-
break
|
600
|
-
else
|
601
|
-
res = res[part]
|
602
|
-
end
|
603
|
-
end
|
604
|
-
end
|
605
|
-
res && res[:__dead]
|
606
|
-
end
|
607
|
-
|
608
635
|
# Set this pair in the Query context, but also in the interpeter namespace,
|
609
636
|
# for compatibility.
|
610
637
|
def set_interpreter_context(key, value)
|
@@ -623,7 +650,7 @@ module GraphQL
|
|
623
650
|
query.resolve_type(type, value)
|
624
651
|
end
|
625
652
|
|
626
|
-
if
|
653
|
+
if lazy?(resolved_type)
|
627
654
|
GraphQL::Execution::Lazy.new do
|
628
655
|
query.trace("resolve_type_lazy", trace_payload) do
|
629
656
|
schema.sync_lazy(resolved_type)
|
@@ -634,22 +661,14 @@ module GraphQL
|
|
634
661
|
end
|
635
662
|
end
|
636
663
|
|
637
|
-
def authorized_new(type, value, context
|
638
|
-
|
639
|
-
|
640
|
-
auth_val = context.query.trace("authorized", trace_payload) do
|
641
|
-
type.authorized_new(value, context)
|
642
|
-
end
|
664
|
+
def authorized_new(type, value, context)
|
665
|
+
type.authorized_new(value, context)
|
666
|
+
end
|
643
667
|
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
end
|
649
|
-
end
|
650
|
-
else
|
651
|
-
auth_val
|
652
|
-
end
|
668
|
+
def lazy?(object)
|
669
|
+
@lazy_cache.fetch(object.class) {
|
670
|
+
@lazy_cache[object.class] = @schema.lazy?(object)
|
671
|
+
}
|
653
672
|
end
|
654
673
|
end
|
655
674
|
end
|